完整閲讀本文大約需要二十分鐘時間,可根據文章結構圖直接閲讀自己需要的部分。
1. Cookie 產生的背景
所有新技術的出現都是為了解決某一痛點。 ——《前端三昧》
我們都知道,HTTP 協議是無狀態的,服務器無法知道兩個請求是否來自同一個瀏覽器,也不知道用户上一次做了什麼,每次請求都是完全相互獨立,這嚴重阻礙了交互式 Web 應用程序的實現。例子:
- 購物車:在典型的網上購物場景中,用户瀏覽了幾個頁面,買了一盒餅乾和兩瓶飲料。最後結帳時,由於
HTTP的無狀態性,不通過額外的手段,服務器並不知道用户到底買了什麼。 - 登錄狀態:我們常用的“記住密碼”功能,在以前如果不是用
Cookie記住了登錄憑據,想要實現該功能將會很複雜。
正是為了解決這些交互方面存在的痛點,Cookie 應運而生。
2. Cookie 概述
Cookie( 也叫Web Cookie或瀏覽器 Cookie)是服務器發送到用户瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。
存儲 Cookie 是瀏覽器提供的功能。Cookie 其實是存儲在瀏覽器中的純文本,瀏覽器的安裝目錄下會專門有一個 Cookie 文件夾來存放各個域下設置的 Cookie(非內存 Cookie)。
通常,它用於告知服務端兩個請求是否來自同一瀏覽器,或者用來保存一些狀態信息,Cookie 使基於無狀態的 HTTP 協議記錄穩定的狀態信息成為了可能。常用的有以下方面:
- 對話(
session)管理:保存登錄、購物車等需要記錄的信息。 - 簡單的緩存:存儲一些簡單的業務數據,比如購物車等需要記錄的信息。
- 個性化:保存用户的偏好,比如網頁的字體大小、背景色等等。
- 追蹤:記錄和分析用户行為。
Cookie 主要是用來存儲狀態的。
Cookie 曾一度用於客户端數據的存儲,因當時並沒有其它合適的存儲辦法而作為唯一的存儲手段。現在來説,這樣做雖然可行,但是並不推薦,因為 Cookie 的設計目標並不是這個,它:
- 容量很小( 4KB )
- 缺乏數據操作接口
- 影響性能
客户端儲存應該更多的考慮使用 localStorage 、sesseionStorage 和 IndexedDB。
查看瀏覽器上存儲的 Cookie 的方法如下圖:
當然,瀏覽器可以設置不接受 Cookie,也可以設置不向服務器發送 Cookie。window.navigator.cookieEnabled屬性返回一個布爾值,表示瀏覽器是否打開 Cookie 功能。
// 瀏覽器是否打開 Cookie 功能
window.navigator.cookieEnabled // true
本文所有的討論都是在瀏覽器的 window.navigator.cookieEnabled 為 true 的前提下進行的。
3. Cookie 的工作流程
4. Cookie 的限制
4.1 格式限制
Cookie 只能存儲純文本格式,因為:
- 每條
Cookie的大小有限制 - 為用户信息安全考慮,
Cookie中存儲的是不可執行語句
4.2 大小和條數限制
由於 Cookie 是保存在客户端上的,所以瀏覽器加入了一些限制確保 Cookie 不會被惡意使用,同時不會佔據太多磁盤空間,所以 Cookie 的數量和大小是有限的。
不同瀏覽器對 Cookie 數量和大小的限制,是不一樣的。一般來説,單個域設置的 Cookie 不應超過 50個,每個 Cookie 的大小不能超過 4KB 。超過限制以後,Cookie 將被忽略,不會被設置。
其限制的原因,主要在於阻止Cookie的濫用,而且Cookie會被髮送到服務器端,如果數量太大的話,會嚴重影響請求的性能。以上這兩個限制條件,就是Cookie為什麼會被瀏覽器自動刪除的原因了。
4.3 域限制
不可跨域讀取,Cookie 是被哪個域寫入的,就只能被這個域及其子域讀取。比如:
由 test.com 寫入的 Cookie 可以被 test.com 和 test.com/child 讀取,而不能被 example.com 讀取。
4.4 路徑限制
存儲 Cookie 時會指定路徑,該路徑的子級可以讀取該 Cookie,但是它的父級卻讀取不到——子可以讀取父,但父不能拿到子,例如:
由 test.com/parent/child 存儲下的 Cookie,可以被 test.com/parent/child/child 讀取,但不能被 test.com/parent 讀取。
一般會將 Cookie 存在根路徑下,可以避免這種情況的發生。
4.5 時效限制
每個 Cookie 都有時效性,默認的有效期是會話級別( Seesion Cookie ):就是當瀏覽器關閉,那麼 Cookie 立即銷燬,但是我們也可以在存儲的時候手動設置 Cookie 的過期時間,具體設置方法會在下文講到。
5. Cookie 的屬性
5.1 Name/Value
設置 Cookie 的名稱及相對應的值,對於認證 Cookie,Value 值包括 Web 服務器所提供的訪問令牌 。
5.2 Domain
指定了可以訪問該 Cookie 的 Web 站點或域。
Cookie 機制並未遵循嚴格的同源策略,允許一個子域可以設置或獲取其父域的 Cookie。
當需要實現單點登錄方案時,Cookie 的上述特性非常有用,然而也增加了 Cookie 受攻擊的危險,比如攻擊者可以藉此發動會話定置攻擊。因而,瀏覽器禁止在 Domain 屬性中設置 .org、.com 等通用頂級域名、以及在國家及地區頂級域下注冊的二級域名,以減小攻擊發生的範圍。
5.3 Path
Path 標識指定了主機下的哪些路徑可以接受 Cookie(該 URL 路徑必須存在於請求 URL 中)。以字符 %x2F ("/") 作為路徑分隔符,子路徑也會被匹配。
5.4 Expires/Max-Age
設置 Cookie 的生存期。有兩種存儲類型的 Cookie :會話性與持久性。
Expires 屬性指定一個具體的到期時間,到了這個指定的時間之後,瀏覽器就不再保留這個 Cookie ,它的值是 UTC 格式,可以使用 Date.prototype.toUTCString() 格式進行轉換。
Max-Age 屬性制定了從現在開始 Cookie 存在的秒數,比如 60 60 24 * 365(即一年)。過了這個時間以後,瀏覽器就不再保留這個 Cookie
如果沒有設置這兩個選項,則會使用默認值。domain的默認值為設置該Cookie的網頁所在的域名,path默認值為設置該Cookie的網頁所在的目錄。
Expires屬性缺省時,為會話性Cookie(Session Cookie),僅保存在客户端內存中,並在用户關閉瀏覽器時失效。- 持久性
Cookie會保存在用户的硬盤中,直至生存期到或用户直接在網頁中單擊“註銷”等按鈕結束會話時才會失效。
當 Cookie 的過期時間被設定時,設定的日期和時間只與客户端相關,而不是服務端。
5.5 HTTPOnly
這個選項用來設置 Cookie 是否能通過 JavaScript 去訪問。默認情況下, Cookie 不會帶 HTTPOnly 選項(即為空),所以默認情況下,客户端是可以通過 JavaScript 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie 的。當 Cookie 帶 HTTPOnly 選項時,客户端則無法通過js代碼去訪問(包括讀取、修改、刪除等)這個 Cookie 。
用於防止客户端腳本通過 document.cookie 屬性訪問 Cookie ,有助於保護 Cookie 不被跨站腳本攻擊竊取或篡改。但是,HTTPOnly 的應用仍存在侷限性,一些瀏覽器可以阻止客户端腳本對 Cookie 的讀操作,但允許寫操作;此外大多數瀏覽器仍允許通過 XMLHTTP 對象讀取 HTTP 響應中的 Set-Cookie 頭 。
在客户端是不能通過JAvaScript代碼去設置一個httpOnly類型的Cookie的,這種類型的Cookie只能通過服務端來設置。
5.6 Secure
指定是否使用 HTTPS 安全協議發送 Cookie 。
使用 HTTPS 安全協議,可以保護 Cookie 在瀏覽器和 Web 服務器間的傳輸過程中不被竊取和篡改。該方法也可用於 Web 站點的身份鑑別,即在 HTTPS 的連接建立階段,瀏覽器會檢查 Web 網站的 SSL 證書的有效性。
但是基於兼容性的原因(比如有些網站使用自簽署的證書)在檢測到 SSL 證書無效時,瀏覽器並不會立即終止用户的連接請求,而是顯示安全風險信息,用户仍可以選擇繼續訪問該站點。由於許多用户缺乏安全意識,因而仍可能連接到 Pharming 攻擊所偽造的網站 。
如果當前協議是 HTTP,瀏覽器會自動忽略服務器發來的 Secure。
5.7 SameSite
Cookie 允許服務器要求某個 Cookie 在跨站請求時不會被髮送,(其中 Site 由可註冊域定義),從而可以阻止跨站請求偽造攻擊(CSRF)。
SameSite cookies 是相對較新的一個字段,所有主流瀏覽器都已經得到支持。下面是例子:
Set-Cookie: key=value; SameSite=Strict
SameSite 可以有下面三種值:
None。瀏覽器會在同站請求、跨站請求下繼續發送Cookies,不區分大小寫。Strict。瀏覽器將只在訪問相同站點時發送Cookie。(在原有Cookies的限制條件上的加強)。Lax。與Strict類似,但用户從外部站點導航至URL時(例如通過鏈接)除外。 在新版本瀏覽器中,為默認選項,Same-site cookies將會為一些跨站子請求保留,如圖片加載或者frames的調用,但只有當用户從外部站點導航到URL時才會發送。如 link 鏈接。
以前,如果SameSite屬性沒有設置,或者沒有得到運行瀏覽器的支持,那麼它的行為等同於None,Cookies會被包含在任何請求中——包括跨站請求。大多數主流瀏覽器正在將
SameSite的默認值遷移至Lax。如果想要指定Cookies在同站、跨站請求都被髮送,現在需要明確指定SameSite為None。
5.8 Cookie prefixes
Cookie 機制的使得服務器無法確認 Cookie 是在安全來源上設置的,甚至無法確定 Cookie 最初是在哪裏設置的。
子域上的易受攻擊的應用程序可以使用 Domain 屬性設置 Cookie ,從而可以訪問所有其他子域上的該 Cookie 。會話定置攻擊中可能會濫用此機制。
但是,作為 深度防禦措施,可以使用 Cookie 前綴來斷言有關 Cookie 的特定事實。有兩個前綴可用:
__Host-如果
Cookie名稱具有此前綴,則僅當它也用Secure屬性標記,是從安全來源發送的,不包括Domain屬性,並將Path屬性設置為/時,它才在Set-Cookie標頭中接受。這樣,這些Cookie可以被視為 "domain-locked”。__Secure-如果
Cookie名稱具有此前綴,則僅當它也用Secure屬性標記,是從安全來源發送的,它才在Set-Cookie標頭中接受。該前綴限制要弱於__Host-前綴。
帶有這些前綴點 Cookie, 如果不符合其限制的會被瀏覽器拒絕。請注意,這確保瞭如果子域要創建帶有前綴的 Cookie,那麼它將要麼侷限於該子域,要麼被完全忽略。由於應用服務器僅在確定用户是否已通過身份驗證或 CSRF 令牌正確時才檢查特定的 Cookie 名稱,因此,這有效地充當了針對會話劫持的防禦措施。
Cookie 各個屬性的兼容性如下圖所示:
6. HTTP Cookie 和 document.cookie
6.1 HTTP Cookie
服務器如果希望在瀏覽器保存 Cookie,就要在 HTTP 迴應的頭信息裏面,放置一個Set-Cookie字段。
瀏覽器收到響應後通常會保存下 Cookie,之後對該服務器每一次請求中都通過 Cookie 請求頭部將 Cookie 信息發送給服務器。另外,Cookie 的過期時間、域、路徑、有效期、適用站點都可以根據需要來指定。
HTTP 迴應可以包含多個 Set-Cookie 字段,即在瀏覽器生成多個 Cookie。下面是一個例子。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
除了 Cookie 的值,Set-Cookie字段還可以附加 Cookie 的屬性。
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
一個 Set-Cookie 字段裏面,可以同時包括多個屬性,沒有次序的要求。
如果服務器想改變一個早先設置的Cookie,必須同時滿足四個條件:Cookie的key、domain、path和secure都匹配。否則,會創建一個新的Cookie。
瀏覽器接收了響應頭提供的 Cookie 之後,每一次訪問該域時,都會攜帶該 Cookie 值:
Cookie 字段可以包含多個 Cookie,使用分號(;)分隔。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
6.2 document.cookie
通過 document.cookie 屬性可創建新的 Cookie,也可通過該屬性訪問非 HttpOnly 標記的 Cookie。
上圖從 document.cookie 一次性讀出多個 Cookie,它們之間使用分號分隔。必須手動還原,才能取出每一個 Cookie 的值。
寫入的時候,Cookie 的值必須寫成 key=value 的形式。注意,等號兩邊不能有空格。另外,寫入 Cookie 的時候,必須對分號、逗號和空格進行轉義(它們都不允許作為 Cookie 的值),這可以用 encodeURIComponent 方法達到。比如,我們要存儲一個對象到 Cookie中,可以通過下面代碼實現:
設置完成後,在瀏覽器查看:
那要怎麼才能讀取到這次設置的 Cookie 呢?方法如下:
讀取到的結果如下:
document.cookie一次只能寫入一個Cookie,而且寫入並不是覆蓋,而是添加。
7. Cookie 的安全隱患
信息被存在Cookie中時,需要明白Cookie的值時可以被訪問,且可以被終端用户所修改的。根據應用程序的不同,可能需要使用服務器查找的不透明標識符,或者研究諸如JSON Web Tokens之類的替代身份驗證/機密機制。當機器處於不安全環境時,切記不能通過
HTTP Cookie存儲、傳輸敏感信息。
7.1 Cookie 捕獲/重放
攻擊者可以通過木馬等惡意程序,或使用跨站腳本攻擊等手段偷竊存放在用户硬盤或內存中的 Cookie。藉助網絡攻擊手段,包括:
- 在不安全的局域網中被動地監聽網絡通信;
- 通過攻擊網絡用户的路由器,或通過搭建惡意的無線路由器等手法,控制路由基礎設施,將網絡流量重定向到攻擊者控制的主機;
- 發動
DNSPharming(域欺騙)攻擊,通過DNS 緩存中毒、DNS 應答欺騙、或修改用户端的本地域名解析文件等方法攻擊DNS系統,導致用户對合法網站的訪問請求被重定向到惡意網站等等,同樣可能竊取Cookie。
對於捕獲到的認證 Cookie,攻擊者往往會猜測其中的訪問令牌,試圖獲取會話ID、用户名與口令、用户角色、時間戳等敏感信息;或者直接重放該 Cookie,假冒受害者的身份發動攻擊 。
7.2 惡意 Cookies
Cookies 是文本文件, 一般情況下認為它不會造成安全威脅。 但是,如果在 Cookies 中通過特殊標記語言,引入可執行代碼,就很可能給用户造成嚴重的安全隱患。HTML 為區別普通文本和標記語言,用符號 “<>” 來指示 HTML 代碼。 這些 HTML 代碼或者定義 Web 網頁格式,或者引入 Web 瀏覽器可執行代碼段。 Web 服務 器可以使用 Cookies 信息創建動態網頁。假使 Cookies 包含可執行惡意代碼段,那麼在顯示合成有該 Cookies 的網頁時,就會自動執行這段惡意代碼。當然,惡意代碼能否真正造成危害,還取決於 Web 站點的安全配置策略 。
7.3 會話定置
會話定置(Session Fixation)攻擊是指,攻擊者向受害者主機注入自己控制的認證 Cookie 等信息,使得受害者以攻擊者的身份登錄網站,從而竊取受害者的會話信息。
注入 Cookie 的方法包括:
- 使用跨站腳本或木馬等惡意程序;
- 或偽造與合法網站同域的站點,並利用各種方法欺騙用户訪問該仿冒網站,從而通過HTTP響應中的Set-Cookie頭將攻擊者擁有的該域Cookie發送給用户等。
7.4 CSRF 攻擊
跨站請求偽造(Cross-Site Request Forgery,簡稱CSRF)是指:
攻擊者可能利用網頁中的惡意代碼強迫受害者瀏覽器向被攻擊的 Web 站點發送偽造的請求,篡奪受害者的認證 Cookie 等身份信息,從而假冒受害者對目標站點執行指定的操作。
Firefox、Opera 等瀏覽器使用單進程機制,多個窗口或標籤使用同一個進程,共享 Cookie 等會話數據。IE 則混合使用單進程與多進程模式,一個窗口中的多個標籤,以及使用 “CTRL+N” 或單擊網頁中的鏈接打開的新窗口使用同一進程,共享會話數據;只有直接運行IE可執行程序打開窗口時,才會創建新的進程。Chrome 雖然使用多進程機制,然而經測試發現,其不同的窗口或標籤之間仍會共享會話數據,除非使用隱身訪問方式。
因而,用户同時打開多個瀏覽器窗口或標籤訪問互聯網資源時,就為 CSRF 攻擊篡奪用户的會話 Cookie 創造了條件。另外,如果一個Web 站點提供持久化 Cookie,則 CSRF 攻擊將更直接、更容易。
緩解 Cookie 攻擊的方法如下:
- 對用户輸入進行過濾來阻止 XSS;
- 任何敏感操作都需要確認;
- 用於敏感信息的 Cookie 只能擁有較短的生命週期;
8. 安全使用 Cookie
有兩種方法可以確保 Cookie 被安全發送,並且不會被意外的參與者或腳本訪問:Secure 屬性和 HttpOnly 屬性。
標記為 Secure 的 Cookie 只應通過被 HTTPS 協議加密過的請求發送給服務端,因此可以預防 man-in-the-middle 攻擊者的攻擊。但即便設置了 Secure 標記,敏感信息也不應該通過 Cookie 傳輸,因為 Cookie 有其固有的不安全性,Secure 標記也無法提供確實的安全保障, 例如,可以訪問客户端硬盤的人可以讀取它。
JavaScript Document.cookie API 無法訪問帶有 HttpOnly 屬性的 Cookie;此類 Cookie 僅作用於服務器。例如,例如,持久化服務器端會話的 Cookie 不需要對 JavaScript 可用,而應具有 HttpOnly 屬性。此預防措施有助於緩解跨站點腳本(XSS)攻擊。
9. Cookie 的替代方案
由於 Cookie 在使用上存在較多限制,近年來,隨着技術的發展成熟,出現了幾種可替代 Cookie 的方案,且已被大多數主流瀏覽器支持。
- Web Storage、window.localStorage
在瀏覽器中存儲數據的另一種方法是 Web Storage API。window.sessionStorage 和 window.localStorage 屬性與持續時間中的會話和永久 Cookie 相對應,但是存儲限制比 Cookie大,並且永遠不會發送到服務器。
- IndexedDB
可以使用 IndexedDB API 或基於它構建的庫來存儲更多結構化的數據。
- Web SQL
Web SQL 是一種利用數據庫進行數據存儲並利用 SQL 處理檢索任務的 API。
歡迎大家來到我的「山頭」,我是「前端三昧」的作者 隱逸王 —— 一個想要做山大王的男人!願和你一起領略前端三昧,發現前端之美!