Cookie, Session, token及JWT偽造
前言:HTTP是一種無狀態的協議,而這就意味着如果用戶向瀏覽器提供了用戶名和密碼來進行用戶認證,那么下一次請求時,用戶還要再一次進行用戶認證才行。為了分辨鏈接是誰發起的,需要瀏覽器自己去解決這個問題。而Cookie、Session和Token就是為了解決這個問題而提出來的機制。
Cookie
Cookie是服務端發送給客戶端的用於驗證某一會話信息的數據,Cookie中有很多字段。不同網站Cookie中字段是不一樣的,是由服務器端設置的。Cookie中常放入session id或者token用來驗證會話的登錄狀態。
Cookie的分類
session cookie
當我們打開一個瀏覽器訪問某個網站的時候,該網站服務器會返回一個session cookie,當我們繼續訪問該網站下其他頁面時,用該Cookie驗證我們的身份。所以我們不需要每個頁面都登錄,但是我們關閉瀏覽器重新訪問該網站時,需要重新登錄獲取瀏覽器返回的Cookie。session cookie在訪問一個網站的過程中一般是不變化的,有時也會變化,比如切換不同的權限時Cookie值會變化。
瀏覽器生成session cookie的過程如下:
瀏覽器第一次發送請求(用戶名和密碼)給Web服務器,Web服務器把用戶的登陸信息存在Cookie中發送給瀏覽器,瀏覽器再次訪問該網站攜帶Cookie,直接訪問。
permennent cookie
是保存在客戶端瀏覽器上存儲用戶登錄信息的數據,是由服務端生成發送給瀏覽器的。瀏覽器會將Cookie保存在某個目錄下的文本文件中,下次請求同一網站時就發送該Cookie給服務器。前提是瀏覽器設置開啟了Cookie。
Session
session是保存在服務器端的經過加密的存儲特定用戶會話所需的屬性及配置信息的數據。當我們打開瀏覽器訪問某網站時,session建立,只要瀏覽器不關閉(也有時間限制,可以自己設置超時時間),這個網站就可以記錄用戶的狀態,當瀏覽器關閉時,session結束。
SessionID除了可以保存在Cookie中外,還可以保存在URL中,作為請求的一個參數(sid)。其中,保存在Cookie中的SessionID與session cookie類似
Token
Session的弊端
上文提到Session的信息是保存在服務器端的,隨着不同客戶端用戶的增加,獨立的服務器已無法承載更多的用戶,這時候Session的問題就會暴露出來:
1.每個用戶經過應用認證之后,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑒別,通常而言Session都是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大。
2.用戶認證之后,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味着用戶下次請求還必須要請求在這台服務器上,這樣才能拿到授權的資源,比如說用兩個機器組成一個集群,小明通過機器A登錄系統,那么SessionID就會保存在機器A上,假設下一次請求被轉發到機器B怎么辦?機器B沒有小明的SessionID。有時候會采用session sticky的小技巧,就是讓小明的請求一直粘連在機器A上。但是這也不管用,要是機器A掛掉了,還得轉到機器B上去。這樣的話就只好做SessionID的復制了,即把A機器上的SessionID復制到B機器上。后來memcached提出把SessionID集中存儲在一個地方,所有機器都通過這個地方來訪問。但是這樣一來,一旦負責Session的機器掛掉,所有人就得重新登錄一遍,這種問題也是不願意發生的。
3.CSRF攻擊: 因為Session是基於Cookie來進行用戶識別的,如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。
token的建立
基於以上的問題有人提出了擺脫Session的想法。具體就是不在服務端保存SessionID了,讓客戶端去保存一個服務端生成的token,每次請求的時候附加上這個token,服務端只需要對這個token進行相應的校驗就可以完成身份的驗證了。
由於服務端不再存放SessionID了,也不會有token,那么可能就會有攻擊者偽造token來攻擊。因此服務端需要對token做一些防偽造的的處理,具體就是對數據做一個簽名。譬如用HMAC-SHA256算法加上一個只有服務器知道的密鑰,對數據做一個簽名,然后把簽名和數據一起作為token,由於密鑰別人不知道,就無法偽造token了。服務器並不會保存這個token,當再次將token發到服務器的時候,服務端會用同樣的HMAC-SHA256算法和同樣的密鑰去對數據再計算一次簽名,並和token中的簽名做一個比較,如果相同的話,就可以判斷出已經登陸過,並且可以直接取到userId;如果不相同,則數據部分肯定被人篡改過,這時就能夠做一些身份校驗失敗的相應處理。
這樣一來,服務器就不需要保存SessionID了,只需要生成token,然后校驗token,相當於用CPU計算時間換回了存儲空間。
基於token的鑒權機制
基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不需要去考慮用戶在哪一台服務器登錄了,這就為應用的擴展提供了便利。
流程上是這樣的:
1.用戶使用用戶名密碼來請求服務器
2.服務器進行驗證用戶的信息
3.服務器通過驗證發送給用戶一個token
4.客戶端存儲token,並在每次請求時附送上這個token值
5.服務端驗證token值,並返回數據
這個token必須要在每次請求時傳遞給服務端,它應該保存在請求頭里, 另外,服務端要支持CORS(跨來源資源共享)策略,一般我們在服務端這么做就可以了。
JWT的構成
JWT是由三段信息構成的,將這三段信息文本用.連接在一起就構成了JWT字符串,譬如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SP0R2USEDHqPV7mcIK08ZAs4WtPMQ0NdMHuSD8tnWOw
頭部(header)
JWT的頭部承載兩部分信息:
1.聲明類型:這里是JWT
2.聲明加密算法:通常使用HMAC SHA256
譬如上文第一部分的頭部即為:
{
"alg": "HS256",
"typ": "JWT"
}
的base64加密后的字符串。
JWT支持將算法設定為“None”。如果“alg”字段設為“ None”,那么JWT的第三部分會被置空,這樣任何token都是有效的。這樣就可以偽造token進行隨意訪問。
載荷(payload)
存放有效信息,包含標准中注冊的聲明、公共的聲明、私有的聲明
標准中注冊的聲明 (不強制使用)
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明
可添加任何信息,一般添加用戶的相關信息或必要的信息,不建議添加敏感信息。
私有的聲明
私有聲明是提供者和消費者共同定義的聲明,不建議存放敏感信息。
譬如上文第二部分的載荷即為:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
的base64加密后的字符串。
簽證(signature)
JWT第三部分是一個簽證信息,由三部分組成:** header** (base64加密后的)、payload (base64加密后的)、secret。
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成JWT的第三部分。
譬如上文第三部分的簽證即為:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret(我這里使用的是1234)
)
加鹽secret組合加密后的字符串。
注意:secret是保存在服務器端的,JWT的簽發生成也是在服務器端的,secret就是用來進行JWT的簽發和JWT的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret,那就意味着客戶端是可以自我簽發JWT了。
通過JWT進行認證
JWT的認證
客戶端接收服務器返回的JWT,將其存儲在Cookie或localStorage中。此后,客戶端將在與服務器交互中都會帶JWT。如果將它存儲在Cookie中,就可以自動發送,但是不會跨域,因此一般是將它放入HTTP請求的Header Authorization字段中。當跨域時,也可以將JWT被放置於POST請求的數據主體中。
服務器每次收到信息都會對它的前兩部分進行加密,然后比對加密后的結果是否跟客戶端傳送過來的第三部分相同,如果相同則驗證通過,否則失敗。
JWT本身包含認證信息,因此一旦信息泄露,任何人都可以獲得令牌的所有權限。為了減少盜用,JWT的有效期不宜設置太長。對於某些重要操作,用戶在使用時應該每次都進行進行身份驗證。為了減少盜用和竊取,JWT不建議使用HTTP協議來傳輸代碼,而是使用加密的HTTPS協議進行傳輸。
JWT安全問題
1.修改算法為none
2.修改算法從RS256到HS256
3.信息泄漏 密鑰泄漏
4.爆破密鑰
JWT偽造
第一步:將web服務器返回的JWT數據進行翻譯,我這里使用的是這個https://jwt.io/ 在線工具(也可以用base64去解碼),找到要偽造的內容。
第二步:使用工具爆破秘鑰https://github.com/brendan-rius/c-jwt-cracker 該程序需要在Linux環境下運行。
工具的安裝及使用
終端進入到虛擬機下該文件目錄中,輸入docker build . -t jwtcrack即可進行build,如果報錯的話,證明你的權限不夠,需要將用戶添加到docker用戶組,輸入:
sudo groupadd docker
sudo gpasswd -a $USER docker
newgrp docker
后重啟即可。
build結束后就可以運行了
docker run -it --rm jwtcrack +JWT
等待輸出secret即可。
第三步:在https://jwt.io/ 在線工具中輸入爆破出來的秘鑰進行驗證,如果第三部分的內容與之前一致,證明秘鑰正確,就可以進行修改了,修改后再粘貼生成的JWT利用burpsuite抓包進行修改后發回到服務器中即可實現JWT偽造。
參考:
https://blog.csdn.net/qq_45521281/article/details/106073624
https://blog.csdn.net/whoim_i/article/details/104279181/