Cookie淺析
Cookie
翻閱了好久關於Cookie的部落格及文件,感覺一直有一塊結沒有解開,所以一直難以在腦中形成一個順暢的知識脈絡。最後實在是遭不住,拉上我的大神朋友在食堂裡坐了3個小時,問了個底朝天!總算形成了清晰的知識脈絡了。
學習知識和技術一定要知道
- 是什麼需求或場景導致了該知識或技術的產生
- 該知識或技術的實現原理是什麼
- 該技術是否有什麼缺陷,能否有更好的解決方案或者補救措施
啊,閒話說太多了,讓我們來開始正題吧。
以一個場景為例:
你登入網站A的賬號,想要在這次輸入使用者名稱和密碼登入之後,在一段時間內,重新開啟該頁面的時候,不需要再次輸入賬號名密碼就能夠登入賬號,並收到該網站給你的推送資訊。
很正常的需求,那麼讓我們從程式設計師的角度來看看究竟是如何實現的。
進化之路
儲存使用者登入狀態的思路是這樣一步步進化的:(以下都是假設使用https協議進行通訊,也就是就算被資料抓包,攻擊者也得不到有效的登入資訊)
無狀態的純https通訊 -> 每次登入賬號都需要重新輸入賬號密碼 -> 服務端使用一個flag變數來判斷登入態 -> 服務端使用純Cookie來判斷登入態 -> 服務端使用session配合Cookie來判斷登入態。(當然,可能還會有更安全更高效的方法,限於我才疏學淺,待我學習到了再與大家分享!)
無狀態的純https通訊
大家都知道的是,https是無狀態的通訊協議。
也就是說,你這次與服務端通訊,通訊結束了,斷開TCP連線,等到下次你再與該伺服器進行通訊,伺服器就完全不認識你了,也就無從談起登入狀態儲存了。
該協議這樣設計,確實減小了通訊的壓力,但對於想要儲存登入狀態的我們,實在是讓人困擾。
每次登入賬號都需要重新輸入賬號密碼
這是我們最不想看到的方式,由於https沒有記憶性,所以每次登入我們都需要重新輸入賬號密碼再傳輸給服務端檔案,然後服務端檔案根據傳過來的賬號密碼在資料庫中檢索,若檢索到則判斷使用者登入。
每次都需要輸入賬號密碼才能登入賬號,這當然不行。我們繼續。
使用一個flag變數來判斷登入態
第一次或登入態過期後再一次在ajax裡帶上使用者的使用者名稱與密碼了,服務端接受到請求後,解析出帶過來的資料,然後在資料庫中進行搜尋,是否存有該使用者名稱及密碼,如有,則建立一個變數flag(假設flag值等於1,為登入態有效。為0登入態無效),返回給使用者。往後使用者再與伺服器進行互動時,都帶上flag,服務端根據這個flag進行判斷登入態是否有效。
但是,這個方法本質上就是無法執行的。
因為flag只是一個臨時變數,重新開啟一個tab頁面時臨時變數就都會被清空。也就是說,flag無法在客戶端做到儲存,也就無從談起每次與服務端互動時都帶上這個資料了。
雖然說,該方式不可行,但其思路是有借鑑的意義的——只要我們能夠將flag資料長久地儲存在客戶端中,那麼就能夠做到每次與服務端互動都帶上這個資料了。
這個客戶端儲存工具就是——Cookie。
可能有的同學就說了:“那我們就想辦法不要每次檢查登入態都檢索一次資料庫,這樣做——當用戶第一次(或者是登入態過期後再一次)輸入使用者名稱密碼,將這些資訊通過ajax傳給服務端,服務端接受到後,在資料庫裡檢索,如果檢索到了,則生成一個flag發給客戶端,往後每次客戶端與該伺服器通訊時都帶上這個flag資訊,只要該flag資訊意義為有效,則使用者登入態為有效。”
非常聰明,這就是“使用純Cookie來判斷登入態”的基本思路了。至於該思路的問題我們接下來講。
使用純Cookie來判斷登入態
思路是這樣的:
當用戶第一次(或者是登入態過期後再一次)輸入使用者名稱密碼,將這些資訊通過ajax傳給服務端,服務端接受到後,在資料庫裡檢索,如果檢索到了,則使用set-Cookie生成一個Cookie(為了使服務端不光能夠判斷該登入態有效且能夠知道使用者的身份,所以該Cookie裡得儲存能夠表明使用者身份的資訊,如賬號,密碼。)發給客戶端,往後每次客戶端與該伺服器通訊時都在請求頭帶上這個Cookie資訊,只要該Cookie資訊意義為有效,則使用者登入態為有效,並返回該使用者有關的資訊,也就不需要再次輸入登入的賬號密碼。
這個Cookie的意義就是flag。
這個思路成功地解決了長久儲存flag的問題,不過隨之而來了新的問題。
- Cookie是一個文字檔案,並且直接明文儲存在瀏覽器中,可以直接通過inspect->Application->Cookies(以Chrome瀏覽器為例)中獲得明文的Cookie的資訊,若Cookie儲存的是保密資訊(如銀行的登入使用者名稱密碼),有人使用你的電腦的話,則會被直接看到了,極其不安全。不過,也可以將cookie值進行加密,這樣就算別人看到了你瀏覽器中的cookie,也無法得到其有效資訊
- 可以通過js指令碼的document.Cookies()來獲得Cookie,如果有別有用心的人利用這一點,使用XSS攻擊(稍後會講)的話,那麼你的Cookie就會被他們獲取到了,你的資訊也就完全暴露給了他們。如果你想禁止這個操作,則在伺服器設定Set-Cookie請求頭時,設定httpOnly為true
所以說這個思路也是有很大問題的,不過若是你的網站安全要求不高,甚至說沒有安全要求,那麼用這個思路也是可以的。
使用session配合Cookie來判斷登入態
一步步解決問題,一步步進化,我們終於來到了這一步。不過不是說該方法是完美的,他也有很多問題漏洞,等下就講。
既然我不想我的保密資訊直接暴露在瀏覽器中,那麼我們就在上一種思路的基礎上,修改一個地方——我在Cookie中不直接儲存使用者的保密資訊了,而是將該保密資訊存在session(session可以理解為一個物件,每個session都儲存在session檔案中)中,生成一個session_id(很長的隨機字串)來標識該session,session_id設定在Set-Cookie中。
我們來捋一遍過程:
當用戶第一次(或者是登入態過期後再一次)輸入使用者名稱密碼,將這些資訊通過ajax傳給服務端,服務端接受到後,在資料庫裡檢索,如果檢索到了,建立一個session物件,在該物件中儲存使用者的登入資訊,並生成一個session_id來作為該session的標識,再使用set-Cookie生成一個Cookie(Cookie內容為session_id)發給客戶端,往後每次客戶端與該伺服器通訊時都在請求頭帶上這個Cookie資訊,只要該Cookie中儲存的session_id能夠匹配到session,則使用者登入態為有效,並返回該使用者有關的資訊。
這樣的方式雖然解決了Cookie明文儲存登入資訊的問題(因為客戶端中的Cookie儲存的是很長的隨機字串,不用擔心別人看到你的Cookie內容了就知道你的賬號密碼是多少了)。
但還有個問題沒有解決——可以通過js指令碼的document.Cookies()來獲得Cookie。如果你下載的頁面中有攻擊者嵌入的攻擊程式碼,該程式碼就能讀取到你的Cookie,然後再將得到的Cookie傳給攻擊者自己的伺服器。
那麼,攻擊者是如何嵌入攻擊程式碼的呢?該html頁面的指令碼不都是開發者自己編寫的嗎,攻擊者無法直接修改html檔案的內容呀,總不可能開發者自己寫了攻擊程式碼攻擊自己的網站吧?
這就涉及到了XSS攻擊。
XSS攻擊
在講XSS攻擊之前,我們先提一下同源策略,這是Web安全的一大根基。
同源策略(拒絕對html的dom結構的操作,訪問別的瀏覽器不能帶上不同源的cookie,ajax請求不能傳送)
同源策略:限制了從一個源載入的文件或指令碼與另一個源的資源進行互動。(同源的意思為:相同協議,域名,埠)
會禁止如下跨域操作:
- js使用ajax請求不同源的指令碼檔案
- js操作不同源的dom
- js獲得不同源的cookie,localStorage,sessionStorage資訊
但不禁止如下跨域操作:
- 使用script,link,img標籤時,跨域發起請求(即指定的src即使是不同源的,對應的後端檔案也會響應請求)
例如說不同源的網站A向網站B發起ajax請求,由於兩者不同源,網站B會拒絕響應網站A的請求。這樣的話,就防止了無許可權的惡意請求,也就是說,網站B只響應有許可權的請求(即同源檔案的請求),而同源檔案都是由可信賴的合作人編寫的,所以比較安全。
舉個例子:
攻擊者發現了銀行修改金額資訊只需要使用ajax,在攜帶的資訊中帶上修改的金額數,後端檔案接收到該請求會根據該資料來修改金額(當然實際情況不可能這麼簡單),那麼攻擊者只要從自己的伺服器使用對應的url帶上金額資訊,如果沒有同源策略的話,銀行的伺服器會處理該請求,也就修改了銀行的資料庫中的金額資料。這顯然是不安全的。
既然這樣,那麼我們就規定,一個源中的檔案只響應同一個源下的請求,從根源上杜絕不安全程式碼生效。
不過,世上沒有密不透風的牆,你不響應不同源的請求,你只響應同源的請求是吧。那麼我就讓你同源的檔案為我服務,獲取到我想要的資訊併發給我的伺服器。
這我們就回到了XSS攻擊。
在一般情況下,html的內容都是由開發人員設定的。但如果這是個論壇呢?(只要是能夠渲染出使用者的輸入內容的網站就行)
是這樣的,當你輸入發言內容,伺服器將你的發言內容存在了資料庫中,等到別人再次訪問會將你的輸入內容渲染出來,如果說正常文字沒有關係,但如果是帶有script標籤呢?那麼渲染到頁面上的時候,就會被認為是js指令碼,並乖乖地執行該指令碼,給壞人做事。
這樣,攻擊者就能夠在正常的html頁面中嵌入他的攻擊程式碼。如果我們想要做防範措施的話,就是過濾使用者的輸入文字,禁止輸入非法字元。
ok,這個時候攻擊者在你的html頁面嵌入了攻擊者的攻擊程式碼,並且你的html頁面執行了他的攻擊程式碼幫他做了壞事,由於是同源的,所以該攻擊程式碼相當於擁有了所有許可權為所欲為。現在攻擊者已經拿到了他想要的資訊了,不過他還有個問題要解決——如何才能跨域將資料傳輸到他的伺服器中。(因為攻擊者的伺服器絕對是跟你的伺服器是不同源的,所以必須得解決跨域問題,否則攻擊者的伺服器接受不到從別的源中攻擊得到的資料)
一個最簡單粗暴的方法——在嵌入的攻擊程式碼中,執行插入img標籤,並設定其src屬性,設定該屬性的值為攻擊者伺服器的域名並帶上得到的資料作為url引數。
因為img標籤不受同源策略的限制,所以不同源的檔案可以互相響應。
就這樣,使用者得到了你的資訊,並將資料傳到了他的資料庫中為他所用。
歸根結底的防範方法就是:過濾輸入文字,設定字元黑名單。
不過這還是有漏洞的,例如說將<設定為黑名單了,但我使用<的轉義字元,該轉義字元不在黑名單中,還是有漏洞。還是那句話世上沒有不透風的牆。
CSRF攻擊
XSS攻擊是通過注入攻擊程式碼來攻擊獲取你的資訊許可權,而CSRF攻擊則是通過直接使用被攻擊者的Cookie偽裝為被攻擊者,也就有了被攻擊者的許可權,從而進行攻擊。
說的太抽象了,老規矩,舉個例子:
假設你是個學生A,老師是老師B,他是你的大物老師,而你期末考試在他手底下掛了,你想要在正方教育系統中改成績。但是,改成績需要老師的賬號密碼來登陸獲取修改許可權,而你沒有賬號密碼。但是,沒有賬號密碼沒有關係啊,你有他的Cookie就可以了,不就可以偽裝成老師,來修改成績了。
當然,這個時候你也沒有老師B的Cookie,這個時候你想到,如果老師訪問了正方教育系統,然後沒有退出該頁面,即該網站的Cookie還是有效,那麼只要老師再在同一個瀏覽器的不同tab頁面再次訪問正方教育系統,由於同源,不就會自動在請求頭上加上Cookie值了嗎?
所以,只要我們知道正方教育系統對應路由的url,以及引數形式,不就能夠做到偽裝成老師B,並讓伺服器做出你想讓他做的修改。
ok,這時候你不知道從哪個渠道知道了正方教育系統修改成績的url,如:http://域名/xxxx/xxxx/ChangeScore.jsp?id=學號&score=成績
。
這時候,你就自己寫個網站,裡面有個img,其src就為正方教育系統修改成績的url,然後你給老師發封郵件,其中就是該網站的連結。只要老師在登入正方教育系統網站的同時,點開了你郵件中的連結,你的網站就會跨域發起GET請求,而又因為請求頭中帶有正方教育系統給老師B的Cookie(再多嘴一句,Cookie是在同一個瀏覽器,不同tab頁面,訪問同一個源時共享的),所以正方教育系統網站後臺發現該Cookie的session_id能夠找到對應的session,也就驗證了該訪問使用者是許可權使用者,也就進行了該使用者的許可權操作,用傳過來的score引數修改成績。
ok,CSRF攻擊的過程就講完了。
CSRF攻擊,攻擊者不需要像XSS攻擊一樣要拿到使用者資訊才能攻擊,他僅需要的是讓使用者自己(當然,使用者並不知情)去使用Cookie訪問目標網站,只是說,攻擊的內容由攻擊者確定。
那麼,如何防範CSRF呢?
第一種思路,由於CSRF是自動進行的,被攻擊者在不知情的情況下就自動地執行了惡意操作。那麼我們在確認執行操作之前,新增一個確認機制呢?例如說驗證碼,這樣不就阻止了自動的CSRF攻擊嗎?
第二種思路,CSRF的攻擊是無法得到Cookie資訊的,那麼如果網站要求url中還需要Cookie中儲存的一個隨機數呢?也就是伺服器為每次登入狀態生成的隨機數,即為Token,會儲存在Cookie中返回給使用者。
ok,所以就算是使用session加上cookie也不是萬事大吉的,還是有很多漏洞可以鑽。
不過做好相應的防範措施,該方法確實是個不錯的方法。
到這裡,已經為大家梳理好了知識脈絡了,接下來我會大家稍微介紹一個相關的知識。
關於Cookie的知識
cookie的型別
我們按該Cookie的過期時間分為兩類:會話Cookie和持久Cookie。
會話Cookie是一種臨時型Cookie,儲存在記憶體中,只要使用者退出瀏覽器(不是刪除該tab頁面),會話Cookie就會被一次性刪除。設定會話Cookie的方法是:在建立cookie不設定Expires即可。
持久Cookie儲存在硬碟中,不會因退出瀏覽器而清除持久Cookie。但他並不是真正永久型Cookie,set-Cookie中設定一個特定的過期時間(expires)或者有效期(Max-Age),當有效期過了,則持久Cookie失效,也就會清除。
Cookie的屬性內容
domain
Cookie是跨域是被禁止的,例如a.b.com的Cookie不可以傳輸給c.b.com。
但可以使用domain來做到同個頂域下的跨域傳輸。
產生Cookie的伺服器可以向set-Cookie響應首部新增一個Domain屬性來控制與哪些主機通訊時,會在請求頭帶上該Cookie。
不過有幾點要注意的:
- domain的值只能是自己的域或自己的頂域,不能是其他的域。如你不能設定domain值為baidu.com
- 不宣告domain的話,只有本域才能獲得
- 如果聲明瞭domain,一般包含子域名
Path
如果我們不想與該域下的所有路由進行http通訊時都帶上該Cookie,那麼就在set-Cookie中設定對應的Path屬性,來指定與該域下的哪個檔案通訊時,才帶上Cookie頭
Secure
在set-Cookie中設定secure時,那麼只有是https通訊時,才會在請求頭中帶上Cookie。
HttpOnly(禁止使用JavaScript操作Cookie)
一般來說,我們可以直接通過document.cookie來獲取和設定Cookie值。
但顯然這非常的不安全呢,所以我們通過在set-Cookie中設定HttpOnly來禁止JavaScript操作Cookie。這也是防範XSS攻擊的好辦法。
Cookie帶來的效率問題
有必要先解釋一下什麼是靜態資源與動態資源:
- 不由伺服器執行的,而是由瀏覽器執行的就是靜態資源(如html,js,css,圖片檔案)
- 反之就是動態資源,一般是指的資料庫資源
一個網頁中有許多的靜態資源,這也是使用者要訪問的,這樣的話,請求每個靜態資源都需要在請求頭加上cookie,但是,請求靜態資源並不需要進行cookie驗證,這樣也就造成了嚴重的資源浪費,同時也降低了訪問速度。
所以對應的解決方案是:
多域名拆分。其思想是
如伺服器域名為base.com,那麼我們將頁面檔案放在page.base.com這個主機中,將靜態資原始檔放在static.base.com這個主機中。
這樣的話,兩者的域名不同。當訪問static.base.com獲取靜態資源時,就不會傳輸page.base.com中的cookie。
關於session的知識
當距離使用者上一次使用session(即重新登入頁面)的時間間隔大於了失效時間,服務端就會認為客戶端已經停止了活動,就會把session刪除以節省空間。
session是被刪除只有如下幾種情況:
- 超時(一般在30分鐘左右)
- 程式中主動刪除
- 程式關閉
也就是說,不會因為瀏覽器的關閉(也就是通常說的會話結束)而刪除。而之所以大家會有這個錯覺,是因為大部分session機制是使用會話Cookie來儲存session_id的。
只要Cookie(客戶端維護),session(服務端維護)中有一者失效了,則登入態就失效了。
SESSION的內容通常是與使用者資訊相關的資訊: 1. 身份資訊、登陸狀態 2. 使用者的個性配置、許可權列表 3. 其他的一些通用資料(比如購物車)
不過使用session+cookie來儲存登入態有個問題:
- session儲存在服務端中,當用戶訪問量變大時,服務端需要儲存大量的session,對於伺服器壓力很大
- 當伺服器為一個叢集的時候,使用者登入一個伺服器,會將session儲存在這個伺服器中,而再次訪問網站,可能登入的是另一個伺服器,但卻無法從該伺服器中找到對應的session(因為該session在另一個伺服器中存著呢),一般來說我們使用第三方伺服器來做到快取一致,不過不方便
Token
基於此,也就應運而生了token,token無需在服務端中儲存,是用演算法來判斷登入態,過程如下:
- 客戶端使用使用者名稱跟密碼請求登入
- 服務端收到請求,去驗證使用者名稱與密碼
- 驗證成功後,服務端會簽發一個 Token,再把這個 Token 傳送給客戶端
- 客戶端收到 Token 以後可以把它儲存起來,比如放在 Cookie 裡或者 Local Storage 裡
- 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token
- 服務端收到請求,然後去驗證客戶端請求裡面帶著的 Token,如果驗證成功,就向客戶端返回請求的資料
(全文完)
參考資料:
- 淺談CSRF攻擊方式:https://www.cnblogs.com/wangyuyu/p/3388169.html
- cookie的用途,它的優點和缺點:https://blog.csdn.net/shuidinaozhongyan/article/details/78242192
- Session和Token的區別:https://blog.csdn.net/qq_1290259791/article/details/81193914
- 有關什麼情況下session會失效:https://blog.csdn.net/czh500/article/details/80211410
- 聊一聊 cookie:https://segmentfault.com/a/1190000004556040
- 關於cookie的安全性問題:https://segmentfault.com/q/1010000007347730?_ea=1318399
- 這一次帶你徹底瞭解Cookie:https://www.cnblogs.com/zhuanzhuanfe/p/8010854.html