在本文中,我們將探討JSON網絡令牌(JWT)的設計問題以及不當的處理方式是如何讓網站面臨各種高危攻擊的威脅的。由於JWT最常用於身份認證、會話管理和訪問控制機制,因此,這些漏洞有可能危及整個網站及其用戶。
如果還不熟悉JWT及其工作原理,那也不用擔心——我們會順便介紹所有相關細節。此外,我們還提供了一些含有相關漏洞的實驗環境,這樣你就可以針對真實的目標安全地進行滲透測試了。
實驗環境如果您已經熟悉了JWT攻擊背後的基本概念,目前只想在一些現實的、故意易受攻擊的目標上練習這些漏洞的利用方法,則可以通過下面的鏈接來訪問本專題的所有實驗緩解。
要想查看所有JWT實驗環境,請訪問https://portswigger.net/web-security/all-labs#jwt。
需要注意的是,從Burp Suite Professional 2022.5版本開始,Burp Scanner就可以替您自動檢測JWT機制的某些漏洞。目前,這個版本只在我們的Early Adopter發布頻道提供。關於如何切換渠道的更多信息,請參見https://portswigger.net/burp/documentation/desktop/early-adopter。
什麼是JWT? JSON Web令牌(JWT)是一種標準化的格式,用於在系統之間發送經過加密簽名的JSON數據。它們理論上可以包含任何類型的數據,但最常用於發送關於用戶的信息(“聲明”),以進行身份認證、會話處理和訪問控制。
與傳統的會話令牌不同,服務器需要的所有數據都存儲在JWT本身的客戶端。這使得JWT成為高度分佈式網站的熱門選擇,在這些網站中,用戶需要與多個後端服務器進行無縫交互。
JWT格式JWT由3部分組成:頭部、載荷和簽名。這些部分之間用點號隔開,具體如下面的例子所示:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwi c3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1z fl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5eh oxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfAJWT的頭部和載荷部分其實就是用base64url編碼的JSON對象。其中,頭部包含關於令牌本身的元數據,而載荷包含關於用戶的實際“聲明”。例如,您可以對上述令牌的載荷進行解碼,從而得到以下聲明:
{
'iss':'portswigger',
'exp':1648037164,
'name':'CarlosMontoya',
'sub':'carlos',
'role':'blog_author',
'email':'carlos@carlos-montoya.net',
'iat':1516239022
}在大多數情況下,任何有權訪問令牌的人都可以輕鬆地讀取或修改這些數據。因此,任何基於JWT機制的安全性都嚴重依賴於密碼簽名。
JWT簽名頒發令牌的服務器通常通過對頭部和載荷計算哈希值來生成簽名。在某些情況下,它們還對產生的哈希值進行加密處理。但是無論哪種方式,這個過程都涉及一個秘密密鑰。如果不知道這個密鑰,就無法為給定的頭部和載荷生成有效的簽名。這實際上就為服務器提供了一種機制,以驗證自令牌頒發以來沒有任何數據被篡改過,因為對頭部或載荷部分的任何修改,都將意味著簽名不再匹配。
如果您想更好地理解JWTs是如何構造的,可以使用jwt.io上的調試器對任意令牌進行實驗。
JWT、JWS與JWEJWT規範的約束實際上是非常有限的。它只定義了將信息(“聲明”)表示為可以在雙方之間傳輸的JSON對象的格式。在實踐中,JWT並沒有真正作為一個獨立的實體使用。 JWT規範由JSON Web簽名(JWS)和JSON Web加密(JWE)規範組成,它們定義了實際實現JWT的具體方法。
換句話說,JWT通常是指JWS或JWE令牌。當人們使用“JWT”這個術語時,他們幾乎總是指JWS令牌。 JWE的情況也非常相似,只是令牌的實際內容是經過加密的,而不是僅僅經過編碼處理的。
需要說明的是,為簡單起見,在這些資料中,“JWT”主要是指JWS令牌,儘管所述的一些漏洞也可能適用於JWE令牌。
什麼是JWT攻擊?所謂JWT攻擊,是指用戶向服務器發送修改過的JWT,以實現惡意目的。通常情況下,這個目的是通過冒充已經通過身份認證的另一個用戶,以繞過認證和訪問控制。
JWT攻擊的危害是什麼? JWT攻擊的影響通常很嚴重。如果攻擊者能夠用任意值創建自己的有效令牌,他們就能夠提升自己的權限或冒充其他用戶,從而完全接管這些用戶的賬戶。
JWT攻擊的漏洞是如何產生的? JWT漏洞通常是由於應用程序本身對JWT的處理有缺陷而產生的。與JWT有關的各種規範在設計上相對靈活,允許網站開發人員自行決定許多實現細節。這可能會導致他們意外地引入安全漏洞,即使是在使用“身經百戰”的代碼庫時。
這些實現缺陷通常意味著JWT的簽名沒有被正確驗證。這使得攻擊者可以通過令牌的載荷篡改傳遞給應用程序的值。即使簽名得到了嚴格的檢查,它是否真的可以被信任,在很大程度上也取決於服務器的秘鑰是否仍然是“機密的”。如果這個密鑰以某種方式被洩露,或者可以被猜測或破解,那麼攻擊者就可以為任意令牌生成有效的簽名,從而攻陷整個機制。
如何通過Burp Suite處理JWT如果您過去還沒有使用過JWT,我們建議您在嘗試本文中的實驗之前先熟悉Burp Suite的相關功能。
如何利用存在缺陷的JWT簽名驗證根據設計,服務器通常不存儲任何關於其頒發的JWT的信息。相反,每個令牌都是一個完全獨立的實體。雖然這樣做有許多優點,但也引入了一個基本問題——服務器實際上不知道關於令牌的原始內容,甚至不知道原始簽名是什麼。因此,如果服務器沒有正確地驗證簽名,就沒有什麼可以阻止攻擊者對令牌的其他部分進行任意篡改。
例如,考慮一個包含以下聲明的JWT:
{
'username':'carlos',
'isAdmin':false
}如果服務器是根據username來識別會話,那麼,攻擊者就能夠通過修改用戶名來冒充其他已登錄的用戶。同樣,如果isAdmin值被用於訪問控制,攻擊者也可以提通過篡改這個值來實現提權。
在前兩個實驗中,您將看到一些示例,展示了這些漏洞在實際應用程序中的具體表現。
漏洞:接受任意簽名JWT庫通常會提供一個方法來驗證令牌,同時,還會提供另一個方法對其進行解碼。例如,對於Node.js庫jsonwebtoken來說,這兩個方法本別是verify()和decode()。
有時候,開發人員會混淆這兩個方法,只把傳入的令牌傳給decode()方法。這實際上意味著應用程序根本就沒有對簽名進行驗證。
關於通過未驗證的簽名繞過JWT認證的實驗環境,請訪問https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature。
漏洞:接受未簽名的令牌實際上,JWT頭部還包含一個alg參數。該參數的作用,就是告訴服務器對令牌進行簽名時使用的是哪種算法,換句話說,在驗證簽名時需要使用哪種算法。
{
'alg':'HS256',
'typ':'JWT'
}這在本質上是有缺陷的,因為服務器別無選擇,只能隱式地信任提供令牌的用戶的輸入(注意,這些輸入受控於該用戶),而該令牌根本沒有被驗證過。換句話說,攻擊者可以直接影響服務器檢查令牌是否值得信任的方式。
JWT既可以使用一系列不同的算法進行簽名,也可以不簽名。在這種情況下,alg參數被設置為None,表示所謂的'不安全的JWT'。由於這種情況具有顯而易見的安全隱患,因此,服務器通常會拒絕沒有簽名的令牌。然而,由於這種過濾依賴於字符串解析,所以,攻擊者可以使用經典的混淆技術繞過這些過濾器,如混合大寫和非預期的編碼。
需要注意的是,即使令牌是未簽名的,載荷部分也必須以點號結尾。
實驗環境:讀者可以通過https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-flawed-signature-verification提供的環境,來練習如何利用有缺陷的簽名驗證機制來繞過JWT認證。暴力破解密鑰某些簽名算法,如HS256(HMAC + SHA-256),會使用一個任意的、獨立的字符串作為秘密密鑰。就像密碼一樣,這個秘密不能被攻擊者輕易猜到或暴力破解,這是至關重要的。否則,他們就能以任意的頭部和載荷值來創建JWT,然後用密鑰重新給令牌簽名。
在實現JWT應用時,開發人員有時會犯一些錯誤,比如忘記改變默認或占位的密碼。他們甚至可能複制和粘貼在網上找到的代碼片段,然後忘記改變作為示例提供的硬編碼的密碼。在這種情況下,攻擊者使用流行的密碼本,輕鬆對服務器的登陸憑據進行暴力破解。
使用hashcat來暴力破解密鑰我們建議使用hashcat對密鑰進行暴力破解。您可以手動安裝hashcat,但它在Kali Linux上是預裝的,可以直接使用。
如果您使用的是Kali中預構建的VirtualBox映像,而不是裸機安裝程序版本,則可能沒有足夠的內存來運行Hashcat程序。
為此,您只需要一個來自目標服務器的有效的、已簽名的JWT,以及一個眾所周知的密碼字典wordlist。然後,可以運行以下命令,將JWT和wordlist作為參數傳入:
hashcat-a0-m16500jwtwordlistHashcat程序會使用密碼字典wordlist中的每個密碼對JWT的頭部和載荷進行簽名,然後將得到的簽名與服務器的原始簽名進行比較。如果任何一個簽名匹配,hashcat就會以下列格式輸出已識別的密碼,以及其他各種細節:
jwt:identified-secret如果多次運行該命令,則需要包含--show標誌以輸出結果。
由於hashcat在您的機器上本地運行,並且不依賴於向服務器發送請求,所以這個過程會非常快,即使在使用龐大的密碼字典Wordlist時也是如此。
一旦確定了密鑰,就可以使用它為您喜歡的任何JWT頭部和載荷生成有效簽名。有關如何利用Burp Suite重新給修改後的JWT簽名的詳細信息,請參見相關章節。
實驗環境:通過弱簽名密鑰繞過JWT身份驗證的實驗,請訪問https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-weak-signing-key。
如果服務器使用了非常弱的密碼,甚至能夠用遍歷字符的方式進行暴力破解,而不必使用Wordlist。
JWT頭部參數注入根據JWS規範,只有頭部參數alg是必需的。然而,在實踐中,JWT頭部(也稱為JOSE頭部)通常包含其他幾個參數。以下是攻擊者特別感興趣的參數:
jwk(JSON Web Key):提供一個表示密鑰的嵌入式JSON對象。
jku(JSON Web Key Set URL):提供一個URL,服務器可以從中獲取一組包含正確密鑰的密鑰。
kid(Key ID):提供一個ID,在有多個密鑰可供選擇的情況下,服務器可以使用該ID來識別正確的密鑰。根據密鑰的格式,它可能還有一個匹配的kid參數。
正如你所看到的,這些用戶可控制的參數用於告訴接收方服務器在驗證簽名時使用哪些密鑰。在本節中,你將學習如何利用這些參數來注入修改過的JWT,而這些JWT都是用你自己的任意密鑰而非服務器的密鑰來簽名的。
通過jwk參數注入自簽名的JWTJSON Web簽名(JWS)規範描述了一個可選的jwk頭部參數,服務器可以用它將其公鑰直接嵌入JWK格式的令牌本身。
JWKJWK(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'
}
}對於不熟悉“公鑰”和“私鑰”這兩個術語讀者,請參閱https://portswigger.net/web-security/jwt/algorithm-confusion#symmetric-vs-asymmetric-algorithms。
理想情況下,服務器應該只使用有限的公鑰白名單來驗證JWT簽名。然而,配置錯誤的服務器有時會使用jwk參數中嵌入的任何密鑰來驗證簽名。
因此,攻擊者可以利用這種行為,用自己的RSA私鑰對修改過的JWT進行簽名,然後在jwk頭部中嵌入對應的公鑰。
雖然我們也可以在Burp中手動添加或修改jwk參數,但JWT編輯器擴展提供了一個非常方便的功能,用於幫助我們測試這個漏洞。
1、在加載該擴展後,在Burp的主選項卡欄中,轉到JWT Editor Keys選項卡。
2、創建一個新的RSA密鑰。
3、向Burp Repeater發送一個包含JWT的請求。
4、在消息編輯器中,切換到擴展生成的JSON Web Token選項卡,並以你喜歡的方式修改令牌的載荷。
5、點擊Attack按鈕,然後選擇Embedded JWK。當收到提示時,選擇新生成的RSA密鑰。
6、發送請求,測試服務器的響應情況。
您也可以通過自己添加jwk頭部來手動執行這種攻擊。然而,您可能還需要更新JWT的頭部參數kid,以匹配嵌入的密鑰的kid。實際上,該擴展的內置攻擊可以替我們完成這個步驟。
實驗環境:讀者可以通過https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jwk-header-injection,來了解如何通過注入jwk頭部來繞過JWT認證。
通過jku參數注入自簽名的JWT實際上,有些服務器並不會直接使用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解析的差異來繞過這種過濾機制。關於這方面的例子,請參閱https://portswigger.net/web-security/ssrf#ssrf-with-whitelist-based-input-filters。
實驗環境:讀者可以通過https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jku-header-injection,來練習如何通過注入jku頭部來繞過JWT認證。
通過kid參數注入自簽名的JWT服務器可能會使用多個加密密鑰來為不同類型的數據進行簽名,而不僅僅是JWT。出於這個原因,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,它存在於大多數Linux系統中。由於這是一個空文件,讀取它時將返回null。因此,用一個Base64編碼的null字節來給令牌簽名將得到一個有效的簽名。
實驗環境:讀者可以通過https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-kid-header-path-traversal,來練習如何通過kid頭部路徑遍歷漏洞來繞過JWT驗證。
如果服務器將其驗證密鑰存儲在數據庫中,kid頭部參數也是一個潛在的SQL注入攻擊的載體。
其他有趣的JWT頭部參數以下頭部參數也可能是攻擊者感興趣的:
●cty(內容類型):有時用於聲明JWT載荷中內容的媒體類型。通常情況下,會省略該參數,但底層解析庫可能還是支持它。如果已經找到了繞過簽名驗證的方法,可以嘗試注入cty參數,將內容類型改為text/xml或application/x-java-serialized-object,這有可能為XXE和反序列化攻擊提供新的向量。
●x5c(X.509證書鏈):有時用於傳遞用於對JWT進行數字簽名的X.509公鑰證書或證書鏈。這個頭部參數可用於注入自簽證書,類似於上面討論的jwk頭部注入攻擊。由於X.509格式及其擴展的複雜性,解析這些證書也很可能會引入漏洞。這些攻擊的細節超出了本文的討論範圍,但要了解更多細節,請參考CVE-2017-2800和CVE-2018-2633漏洞的相關資料。
JWT算法混淆即使服務器使用了攻擊者無法破解的強大密碼,他們仍然可以通過使用開發人員沒有預料到的算法簽名令牌來偽造有效的JWT。這就是所謂的算法混淆攻擊。關於該攻擊方法的詳細介紹,請訪問這篇文章:https://portswigger.net/web-security/jwt/algorithm-confusion。
如何防禦JWT攻擊您可以通過採取以下措施來保護自己的網站免受本文介紹的各種攻擊:
使用最新的庫來處理JWT,並確保開發人員完全了解它是如何工作的,以及所帶來的任何安全影響。現代代碼庫的使用,降低了在代碼實現中引入安全漏洞的可能性,但由於相關規範固有的靈活性,這也不是萬無一失的。
確保對收到的任何JWT進行嚴格的簽名驗證,並考慮邊緣情況,如使用非預期的算法簽名的JWT。
為jku頭部提供允許主機白名單,並嚴格執行。
確保不會受到通過kid頭部參數進行路徑穿越或SQL注入的影響。
JWT處理的其他最佳實踐我們建議在您的應用程序中使用JWT時遵守以下最佳實踐:
始終為頒發的任何令牌設置一個到期日。
盡可能避免通過URL參數發送令牌。
提供aud聲明(或類似內容),以指定令牌的預期接收者。這可以防止它被用在不同的網站上。
讓頒發服務器能夠撤銷令牌(例如,在註銷時)。