目錄
- JWT介紹
- JWT的定義
- JWT的由來
- JWT的構成
- Header
- Payload
- Signature
- JWT和Token的差別
- JWT使用方式
- JWT的安全風險
- 1.敏感資訊洩露
- 2.未校驗簽名
- 3.簽名算法可被修改為none(CVE-2015-2951)
- 4.簽名密鑰可被爆破
- 5.修改非對稱密碼算法為對稱密碼算法(CVE-2016-10555)
- 6.僞造密鑰(CVE-2018-0114)
- JWT tool
- JWT的使用建議
- 參考文檔
JWT介紹
JWT的定義
Json web token (簡稱JWT),是目前最流行的跨域認證解決方案,是一種認證授權機制。
JWT 是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放标準。該token被設計為緊湊且安全的,特别适用于分布式站點的單點登入(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便于從資源伺服器擷取資源,也可以增加一些額外的其它業務邏輯所必須的聲明資訊,該token也可直接被用于認證,也可被加密。
JWT的由來
- HTTP協定本身是無連接配接、無狀态的。而這對于單純的浏覽型網頁是十分友好的,它不需要記住是誰發送的請求,每一個請求對于它來說都是全新的,伺服器也不需要額外的資源去記憶沒有個使用者。
- 但是我們現在面臨的網站大部分都需要管理不同的使用者,他們有不同的身份,例如遊客、普通使用者、管理者、超級管理者等等。而這時我們就需要管理會話,需要認識每一個使用者,知道他們的具體資訊。這時有人就想出了一個辦法,那就是給大家都發一個會話辨別(session id),這個會話辨別是一串很長的随機字元串,每個人都不一樣, 然後每次大家發起HTTP請求的時候,把這個字元串給發送給伺服器, 這樣伺服器就能區分開誰是誰了。
- 不過這種方法雖然解決了基本的認證問題,但是卻給伺服器帶來了巨大的負擔。每個人隻需要儲存自己的session id,而伺服器要儲存所有人的session id, 如果通路伺服器的使用者多了, 甚至需要儲存成千上萬,甚至幾十萬個使用者的資料,這對伺服器說是一個巨大的開銷 ,而且 嚴重的限制了伺服器擴充能力。比如說我用兩個機器組成了一個叢集, 使用者小明通過機器A登入了系統, 那session id會儲存在機器A上, 假設小明的下一次請求被轉發到機器B怎麼辦? 機器B可沒有小明的 session id啊。這時有人提議将所有的session id儲存到一個地方,讓所有的伺服器到這個機器中去取資料,這樣一來,就不用複制了。但是這種方法也有很大的隐患,如果那個負責session 的機器挂了, 所有人就都得重新登入一遍。這種情況有人嘗試把這個單點的機器也搞出叢集,增加可靠性, 但不管如何, 這小小的session 對伺服器來說是一個沉重的負擔
- 于是有人就思考, 我為什麼要儲存這可惡的session呢, 隻讓每個用戶端去儲存該多好。是了,我為什麼要儲存session——是為了确認每一個使用者的身份,并防止他們僞造。那麼我可以将使用者的身份資訊用簽名來保證它的不可篡改性,然後讓使用者自己儲存資料。比如說我用HMAC-SHA256 算法,加上一個隻有我才知道的密鑰, 對資料做一個簽名, 把這個簽名和資料一起作為token , 由于密鑰别人不知道, 就無法僞造token了。當使用者把這個token 給我發過來的時候,我再用同樣的HMAC-SHA256 算法和同樣的密鑰,對資料再計算一次簽名, 和token 中的簽名做個比較, 如果相同, 我就知道使用者已經登入過了,并且可以直接取到使用者的user id , 如果不相同, 資料部分肯定被人篡改過, 我就告訴發送者: 對不起,沒有認證。
- 而順着這個思路,有人就想讓使用者儲存更多的資訊,這樣伺服器的負擔就更小了。有人提出将使用者的一些其它資料和user id一起發送給使用者,讓使用者自己儲存,然後利用密鑰保證安全性,這就是JWT。JWT将 Token 和 Payload 加密後存儲于用戶端,服務端隻需要使用密鑰解密進行校驗(校驗也是 JWT 自己實作的)即可,不需要查詢或者減少查詢資料庫,因為 JWT 自包含了使用者資訊和加密的資料。JWT的出現進一步釋放了伺服器的性能。
JWT的構成
實際的 JWT 大概就像下面這樣
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SO0QDZ1UDMmFjMjVTYxQzM3EjNmNjZjNGO5ATNzQTYh9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
它是一個很長的字元串,中間用點(.)分隔成三個部分。JWT 内部是沒有換行的,這裡隻是為了便于展示,将它寫成了幾行。
JWT 的三個部分依次如下。
Header(頭部)
Payload(負載)
Signature(簽名)
Header
Header 部分是一個 JSON 對象,描述 JWT 的中繼資料,通常是下面的樣子。
{
"alg":"HS256",
"typ":"JWT"
}
jwt的頭部承載兩部分資訊:
- 聲明類型:typ屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫為JWT。
- 聲明加密的算法:通常直接使用 HMAC SHA256(寫成 HS256)
最後,将上面的 JSON 對象使用 Base64URL 算法轉成字元串。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Base64URL
Base64URL和Base64 算法基本類似,但有一些小的不同。
JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字元+、/和=,在 URL 裡面有特殊含義,是以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。
Payload
載荷就是存放有效資訊的地方。這個名字像是特指飛機上承載的貨品,這些有效資訊包含三個部分
- 标準中注冊的聲明
- 公共的聲明
- 私有的聲明
标準中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發者
- sub: jwt所面向的使用者
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發時間
- nbf: 定義在什麼時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份辨別,主要用來作為一次性token,進而回避重播攻擊。
公共的聲明 : 公共的聲明可以添加任何的資訊,一般添加使用者的相關資訊或其他業務需要的必要資訊.但不建議添加敏感資訊,因為該部分在用戶端可解密.
私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感資訊,因為base64是對稱解密的,意味着該部分資訊可以歸類為明文資訊。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然後将其進行base64url加密,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
Signature
Signature 部分是對前兩部分的簽名,防止資料篡改。
首先,需要指定一個密鑰(secret)。這個密鑰隻有伺服器才知道,不能洩露給使用者。然後,使用 Header 裡面指定的簽名算法(預設是 HMAC SHA256),按照下面的公式産生簽名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出簽名以後,把 Header、Payload、Signature 三個部分拼成一個字元串,每個部分之間用"點"(.)分隔,就可以傳回給使用者。
JWT和Token的差別
相同點:
- 都是通路資源的令牌
- 都可以記錄使用者的資訊
- 都是使服務端無狀态化
- 都是隻有驗證成功後,用戶端才能通路服務端上受保護的資源
不同點:
- Token:服務端驗證用戶端發送過來的 Token 時,還需要查詢資料庫擷取使用者資訊,然後驗證 Token 是否有效。
- JWT:将 Token 和 Payload 加密後存儲于用戶端,服務端隻需要使用密鑰解密進行校驗(校驗也是 JWT 自己實作的)即可,不需要查詢或者減少查詢資料庫,因為 JWT 自包含了使用者資訊和加密的資料。
普通token需要後端存儲與使用者的對應關系,而JWT自身攜帶對應關系。在大型的多系統中,普通token每次請求需要向使用者資源伺服器擷取對應使用者資訊同時驗證token,而JWT隻需要驗證簽名有效即可信任且JWT自帶使用者資訊, 無需額外請求。
JWT使用方式
一般是在請求頭裡加入
Authorization
,并加上
Bearer
标注:
Authorization: Bearer + <token>
這是一種無狀态身份驗證機制,因為使用者狀态永遠不會儲存在伺服器記憶體中。 伺服器受保護的路由将在授權頭中檢查有效的JWT,如果存在,則允許使用者通路受保護的資源。 由于JWT是獨立的,所有必要的資訊都在那裡,減少了多次查詢資料庫的需求。
這使得我們可以完全依賴無狀态的資料API,甚至向下遊服務提出請求。 無論哪些域正在為API提供服務并不重要,是以不會出現跨域資源共享(CORS)的問題,因為它不使用Cookie
JWT的認證流程:
- 使用者輸入使用者名/密碼登入,服務端認證成功後,會傳回給用戶端一個 JWT
- 用戶端将 token 儲存到本地(通常使用 localstorage,也可以使用 cookie)
- 當使用者希望通路一個受保護的路由或者資源的時候,需要請求頭的 Authorization 字段中使用Bearer 模式添加 JWT。
- 服務端的保護路由将會檢查請求頭 Authorization 中的 JWT 資訊,如果合法,則允許使用者的行為
JWT的安全風險
1.敏感資訊洩露
我們能夠輕松解碼payload和header,因為這兩個都隻經過Base64Url編碼,而有的時候開發者會誤将敏感資訊存在payload中。
一般我們遇到jwt字元串可以到https://jwt.io/這個網站解密。
2.未校驗簽名
某些服務端并未校驗JWT簽名,是以,可以嘗試修改signature後(或者直接删除signature)看其是否還有效。
3.簽名算法可被修改為none(CVE-2015-2951)
頭部用來聲明token的類型和簽名用的算法等,比如:
{
"alg": "HS256",
"typ": "JWT"
}
以上header指定了簽名算法為HS256,意味着服務端利用此算法将header和payload進行加密,形成signature,同時接收到token時,也會利用此算法對signature進行簽名驗證。
但是如果我們修改了簽名算法會怎麼樣?比如将header修改為:
{
"alg": "none",
"typ": "JWT"
}
那麼服務端接收到token後會将其認定為無加密算法, 于是對signature的檢驗也就失效了,那麼我們就可以随意修改payload部分僞造token。
https://jwt.io将alg為none視為惡意行為,是以,無法通過線上工具生成JWT,可以用python的jwt庫來實作:
import jwt
jwt.encode({"user":"admin","action":"upload"},algorithm="none",key="")
用none算法生成的JWT隻有兩部分了,根本連簽名都不存在。
4.簽名密鑰可被爆破
jwt使用算法對header和payload進行加密,如果我們可以爆破出加密密鑰,那麼也就可以随意修改token了。
JWT爆破腳本:https://github.com/Ch1ngg/JWTPyCrack
也可以使用下面的腳本爆破
jwt_str = "xxx.ttt.zzz"
path = "D:/keys.txt"
alg = "HS256"
with open(path,encoding='utf-8') as f:
for line in f:
key_ = line.strip()
try:
jwt.decode(jwt_str,verify=True,key=key_,algorithm=alg)
print('found key! --> ' + key_)
break
except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
print('found key! --> ' + key_)
break
except(jwt.exceptions.InvalidSignatureError):
continue
else:
print("key not found!")
5.修改非對稱密碼算法為對稱密碼算法(CVE-2016-10555)
JWT的簽名加密算法有兩種,對稱加密算法和非對稱加密算法。
對稱加密算法比如HS256,加解密使用同一個密鑰,儲存在後端。
非對稱加密算法比如RS256,後端加密使用私鑰,前端解密使用公鑰,公鑰是我們可以擷取到的。
如果我們修改header,将算法從RS256更改為HS256,後端代碼會使用RS256的公鑰作為HS256算法的密鑰。于是我們就可以用RS256的公鑰僞造資料
比如說這道CTF題目:https://skysec.top/2018/05/19/2
6.僞造密鑰(CVE-2018-0114)
jwk是header裡的一個參數,用于指出密鑰,存在被僞造的風險。比如CVE-2018-0114: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0114
攻擊者可以通過以下方法來僞造JWT:删除原始簽名,向标頭添加新的公鑰,然後使用與該公鑰關聯的私鑰進行簽名。
比如:
{
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"kid": "TEST",
"use": "sig",
"e": "AQAB",
"n": "oUGnPChFQAN1xdA1_f_FWZdFAis64o5hdVyFm4vVFBzTIEdYmZZ3hJHsWi5b_m_tjsgjhCZZnPOLn-ZVYs7pce__rDsRw9gfKGCVzvGYvPY1hkIENNeBfSaQlBhOhaRxA85rBkg8BX7zfMRQJ0fMG3EAZhYbr3LDtygwSXi66CCk4zfFNQfOQEF-Tgv1kgdTFJW-r3AKSQayER8kF3xfMuI7-VkKz-yyLDZgITyW2VWmjsvdQTvQflapS1_k9IeTjzxuKCMvAl8v_TFj2bnU5bDJBEhqisdb2BRHMgzzEBX43jc-IHZGSHY2KA39Tr42DVv7gS--2tyh8JluonjpdQ"
}
}
JWT tool
此工具可用于測試jwt的安全性,位址是 https://github.com/ticarpi/jwt_tool
示例用法:
JWT的使用建議
- 不要存放敏感資訊在Token裡。
- Payload中的exp時效不要設定太長。
- 開啟Only Http預防XSS攻擊。
- 如果擔心重播攻擊(replay attacks )可以增加jti(JWT ID),exp(有效時間) Claim。
- 在你的應用程式應用層中增加黑名單機制,必要的時候可以進行Block做阻擋(這是針對掉令牌被第三方使用竊取的手動防禦)。
參考文檔
基于Token的身份驗證的原理
什麼是JWT
JWT解密
Cookie、Session、Token、JWT
對JWT的安全測試總結