淺析JWT安全
Part 01、 什么是JWT?
在JWT官網的基本概念中,JSON Web Token (JWT)定義了一個在各方之間通過 JSON 對象安全傳輸信息的方式,它將用戶信息加密保存在JSON格式的token中,服務端將不會保存任何與用戶認證的相關配置信息,只保存校驗token簽名的密鑰,通過簽名的校驗來判斷用戶身份是否合法。此時,這種基于token的身份驗證方法能夠代替傳統的cookie+session身份驗證方法。
1.1 傳統的cookie+session身份驗證方法認證流程
- 用戶端在瀏覽器中輸入用戶名和密碼,發送到服務器。
- 服務器通過密碼校驗后生成session并保存到數據庫中。
- 為用戶端生成一個sessionid,并在瀏覽器中存放擁有sessionid的cookie,當再次發出請求時均會有對這個cookie信息的訪問。
- 服務器獲取用戶cookie,根據其中的sessionid在數據庫中查找,進而判斷請求是否有效。
- 這種session+cookie的模式,是互聯網應用中最為流行的用戶認證模式,但是這種模式的擴展性并不好,如何解決這個問題呢?接下來讓我們繼續認識擴展性更好的JWT認證。
1.2 基于JWT的認證流程
JWT保存在客戶端,服務器端只需要通過密鑰解密技術對JWT進行分析和驗證,不需要再繁瑣地進行數據庫查詢和管理。也就是說服務器不用負擔保存任何保存session數據的任務,減輕服務器壓力,因此比較容易實現擴展。
JWT認證流程:
- 用戶端在瀏覽器中輸入用戶名和密碼,服務器通過密碼校驗后生成JWT。
- 客戶端獲取到token,存儲到cookie或local storage中,當再次發出請求時均會有對這個token信息的訪問。
- 用戶在request請求頭中包含JWT,并發給服務器。
- 服務端將會檢查請求頭中的 JWT 信息,如果正確,則返回響應并允許用戶操作。
Part 02、 JWT格式
物聯網安全是JWT由三部分組成,中間用點(.)分隔,依次如下:Header(頭部)、Payload(負載)、Signature(簽名)。JWT格式如下圖2所示。
2.1 Header(頭部)
JWT的頭部其實就是個JSON對象,它承載著兩部分信息:
- 聲明類型
- 聲明加密的算法
將頭部信息通過Base64URL加密轉成字符串即為JWT頭部。
2.2 Payload(負載)
負載就是存放有效信息的地方,這些有效信息包含三個部分:
- 標準中注冊的聲明
- 公共的聲明
- 私有的聲明
同樣將上面的JSON對象使用Base64URL加密轉成字符串即為JWT負載。
2.3 Signature(簽名)
服務器通常通過散列header和payload來生成簽名。在一些情況下,還會加密生成的哈希值,無論是否加密,過程中都會涉及到簽名密鑰。服務器通過讀取header中指定的簽名算法,按照下表中的公式產生簽名。
簽名機制為服務器提供防篡改的驗證方法:
- 因為簽名是直接從令牌的前兩部分派生的,因此更改header或payload的單個字節,都會導致簽名失配。
- 在不知道服務器的簽名密鑰的情況下,不能為已經給定的header或payload生成正確的簽名。
JWT三個組成部分的解碼展示如下圖3所示。
Part 03、 什么是JWT攻擊?
3.1 JWT攻擊
JWT攻擊是指攻擊者向服務器發送篡改過的JWT,實施惡意操作的行為。一般情況下,攻擊的目的是偽造身份認證的用戶,來繞過身份驗證和訪問控制的阻攔。而如果攻擊者能夠生成任意的有效令牌,就能夠提升用戶權限或假冒其他合法用戶的身份,從而對這些偽造的用戶賬戶實施完全的接管。
3.2 攻擊原理
JSON Web Token(JWT)存在的漏洞往往源于應用程序對JWT的處理存在缺陷,進而影響了其安全性。JWT及其相關規范的設計賦予了網站開發人員較大的自由度,允許自主決定許多實現細節,但是這種自由度也可能導致安全問題的出現。
這些實現缺陷往往直接關聯于JWT簽名的驗證,即便進行了嚴格的簽名驗證,攻擊者仍有可能通過篡改JWT負載中的數據,傳遞惡意值給應用程序,從而干擾正常業務流程。此外,關于傳遞簽名的可信性,主要取決于密鑰的安全性,但是如果服務器的密鑰存在泄漏或被破解,攻擊者將能夠生成合法的簽名,進而偽造令牌并嚴重威脅整個身份驗證與授權機制的完整性。
Part 04、 JWT常見安全漏洞
了解了JWT攻擊原理,再讓我們繼續了解一下JWT相關常見漏洞,主要有以下類型:
1.Accepting arbitrary signatures -- 接受任意簽名
2.Accepting tokens with no signature -- 沒有簽名的令牌
3.Brute-forcing secret keys -- 暴力破解密鑰
4.JWT header parameter injections -- JWT 頭參數注入
- Injecting self-signed JWTs via the jwk parameter --jwk參數注入自簽名
- Injecting self-signed JWTs via the kid parameter -- kid參數注入自簽名
- Injecting self-signed JWTs via the jku parameter --jku參數注入自簽名
4.1 Accepting arbitrary signatures -- 接受任意簽名
JWT庫通常會提供一個驗證token的方法,同時也會提供對其解碼的方法。例如Node.js庫jsonwebtoken的兩個方法:verify()、decode()。如果開發只把decode()方法傳給token而沒有傳遞verify()方法,這意味著攻擊者可以將隨意修改payload后的JWT發送給服務器,服務器僅僅會對JWT進行解碼解析卻沒有驗證簽名是否正確,從而造成偽造用戶、越權訪問等安全問題。
4.2 Accepting tokens with no signature -- 沒有簽名的令牌
JWT頭部有一個重要參數“alg”,它會指明JWT簽名所采用的算法。JWT支持多種算法來進行簽名,也可以不進行簽名。通過修改alg參數為“none”,此時簽名為空,這就是生成了“不安全的JWT”。
4.3 Brute-forcing secret keys -- 暴力破解密鑰
某些簽名算法,會使用一個字符串作為密鑰,而當我們知道JWT簽名算法的時候,就必須要保證這個密鑰不能被腳本輕易猜測或暴力破解,否則攻擊者可以用任意header和payload來簽名創建合法JWT。
4.4 JWT header parameter injections -- JWT頭參數注入
根據JWS規范,只有頭部參數alg是一定要有的。但是在實際生產中,JWT頭部不僅僅有alg參數,往往還包含其它參數。以下三個是實際生活中通常測試攻擊的參數:
? jwk(JSON Web Key):是一種用于表示密鑰的嵌入式 JSON 對象。正常時候服務器應該要對其進行限制,只能使用特定的公鑰白名單進行簽名驗證。然而,如果出現配置失誤或疏忽,某些服務器可能會誤用JWK參數內嵌的任何密鑰進行簽名驗證操作。這就意味著,如果出現jwk配置失誤,攻擊者就可以通過RSA私鑰對已經修改過的payload進行簽名,并將RSA公鑰嵌入到JWK頭部,從而繞過簽名認證。
? kid(Key ID):即密鑰標識符,被用作提供一個獨特的標識,當存在有多個密鑰的情況下,服務器就會通過此ID來準確識別應用于驗證的正確密鑰。但是,系統并不具備辨別用戶意圖的能力,因此如果缺乏對參數的過濾措施,攻擊者就可能篡改密鑰文件,從而實現任意文件讀取攻擊,借此讀取系統內的各類文件資源。
? jku(JSON Web Key Set URL):是一種用于提供URL的機制,發送jwk的地址。與"key ID" (kid) 類似,jku 也可以由用戶根據特定輸入數據來指定。一旦用戶的輸入數據沒有經過嚴格的過濾和驗證,就可能導致潛在的安全漏洞。
上述用戶可操控的參數用于指示接收方服務器在進行簽名驗證時所需的密鑰信息,如果攻擊者修改這些可控參數可能會導致任意文件讀取、SQL注入等安全問題。
Part 05、 JWT安全防護
根據上述安全漏洞,總結有以下防護措施:
1.不要在token中設置任何敏感信息,同時不要將payload中的時間戳失效設定太長,防止被盜的token長時間被濫用。
2.使用最新的庫來處理JWT,并確保完全理解它的工作原理以及任何安全隱患。
3.在服務器端,限制JWT頭部中“alg”參數的值只能使用特定的、安全的算法。禁止使用不安全的算法,如"none"算法。
4.針對JWT頭部進行嚴格的白名單設置,只允許特定的參數值,拒絕任何不在白名單內的參數值。
Part 06、 總結
已簽名的JWT具有標頭、負載和簽名三部分,每個都在確保JWT可用于安全地存儲和傳輸關鍵信息方面發揮著至關重要的身份驗證作用。了解這三個組件對于正確使用JWT至關重要,同時由于簽名的重要性,我們有必要對JWT安全進行了解并做好防護,從而做到防范于未然。