[譯] 理解 Cookie 的 SameSite 屬性

zhangbao90s發表於2020-03-22

原文連結:SameSite cookies explained,by Rowan Merewood

[譯] 理解 Cookie 的 SameSite 屬性

有關 SameSite=None 的實施建議,請參閱第 2 部分:《SameSite Cookie 食譜》

Cookie 可以視作為網站新增持久化狀態的方式之一。多年來,Cookie 的能力不斷髮展壯大,但也遺留了一些問題。 為了解決這個問題,瀏覽器廠商(包括 Chrome、Firefox 和 Edge)正在更改其預設行為,以強制實施更多的保留隱私的預設設定。

每個 Cookie 值是以 key=value 的形式,以及其他一些控制時效和作用範圍等功能的特性組成。你也許已經在使用這些特性設定諸如到期日期之類的資訊,或者指示 Cookie  只在 HTTPS 協議下傳送。伺服器通過在響應中傳送名為 Set-Cookie 標頭屬性設定 Cookie。這方面的所有詳細資訊,超出了本篇的講述範圍,請自行閱讀 RFC6265bis 深入學習瞭解。

假設你搭建了一個部落格,需要向瀏覽使用者展示“新功能”提醒框。 使用者可以關閉改提醒框。你可以將該使用者選項儲存在 Cookie 中,有效期設定為一個月(2,600,000 秒),然後僅通過 HTTPS 傳送。那麼它看起來應該是這樣的:

Set-Cookie: promo_shown=1; Max-Age=2600000; Secure
複製程式碼

image.png

伺服器通過在標頭的 Set-Cookie 欄位設定 Cookie

當使用者瀏覽器的網頁環境符合這些限制條件時,那麼在最近的一個月裡,在向伺服器的請求中都會攜帶此資訊。

Cookie: promo_shown=1
複製程式碼

image.png

瀏覽器在向伺服器請求時,會通過標頭的 Cookie 欄位發回 Cookie

JavaScript 是通過 document.cookie 管理站點的 Cookie 的,比如讀取或新增。新增 Cookie 的方式就是給 document.cookie 賦值。你可以在瀏覽器控制檯中嘗試輸入下面的程式碼:

> document.cookie = "promo_shown=1; Max-Age=2600000; Secure"
< "promo_shown=1; Max-Age=2600000; Secure"
複製程式碼

這是在設定 Cookie,使用的 key 是 promo_shown。接下來,輸入 document.cookie 輸出當前上下文環境的所有可訪問 Cookie 列表,每個 key=value 之間使用 分號+空格 分隔:

> document.cookie;
< "promo_shown=1; color_theme=peachpuff; sidebar_loc=left"
複製程式碼

image.png

使用 document.cookie 訪問 Cookie

如果你在一些受歡迎的站點上嘗試此操作,會注意到大多數站點設定的 Cookie 數量遠遠超過三個。大多數情況下,每一次對域址的請求,都會攜帶這些包含很多含義的 Cookie 傳送出去。對使用者來說,上傳通常比下載受到更多的頻寬限制,因此所有出站請求的開銷增加都會增長第一個位元組傳送出去前的時間延遲。因此,要謹慎設定 Cookie 的數量和大小,利用“最大時效”(Max-Age)屬性控制一些 Cookie 在瀏覽器中的停留的合適時間。

何為第一方 Cookie 和第三方 Cookie?

現在你可以檢視下經常訪問的一些站點 Cookie 資訊,發現一個站點上存在來自各個域址下的 Cookie,而不僅僅當前正在訪問域址的。當前網站域名匹配(即瀏覽器位址列中顯示的地址)的 Cookie 稱為第一方 Cookie,否則就是第三方 Cookie。第一方和第三方是相對概念,同一個 Cookie 在當前網站是第一方,對別的網站而言,是第三方,這取決於使用者當時所出的上下文環境。

image.png

一個頁面中使用的 Cookie 可能來自不同的域址

繼續上面的例子,假設你的一篇部落格文章中包含一張特別好看的貓咪圖片,地址在 /blog/img/amazing-cat.png。別人看到這張圖片,覺得也很好看,就在他的網站上使用了。如此,如果有個使用者訪問了你的這篇文章,然後在你Cookie 中儲存了 promo_shown 欄位,那麼當他在別人的網站上檢視這張圖片時,瀏覽器在發起影像請求的時候會傳送你站點所在域名下的 Cookie 資訊。有時候這隻會徒增請求開銷,因為 promo_shown 欄位並沒在別人的網站上使用。

如果這不是預期效果,那麼為什麼要這樣做呢?通過這種機制,當前站點可以在第三方上下文中時保持已有網站的狀態。 舉個例子,你在自己的網站(第三方上下文)上嵌入了 YouTube 視訊(當前站點),如果訪客之前已經登入了 YouTube,那在這裡你就不需要登入了,當訪客在你的網站上點選了視訊播放器的“稍後觀看”時,跟在原網站上點選是一個意思。

image.png

當訪問不同頁面時,將傳送第三方上下文中的 Cookie

網路的文化屬性之一是預設情況下它很開放開,這讓如此多的人在這裡創造了自己的內容和應用程式的一部分。但也帶來了許多安全隱患和隱私問題。跨站點請求偽造(CSRF)攻擊就是依賴上面講得這種機制,瀏覽器不管是在哪裡(比如,通過第三方網站)發起的本站請求,只要是請求就把本站的 Cookie 資訊跟隨請求一起傳送。舉個例子,如果你正在訪問 evil.example 這個網站,它能觸發對 your-blog.example 地址的請求,你的瀏覽器預設會附加關聯的 Cookie 資訊。如果你的部落格系統並未對這類請求做驗證,那麼在 evil.example 上可能會觸發刪除帖子或新增垃圾內容之類的操作。

使用者也越來越認識到如何使用 Cookie 來跟蹤使用者跨站點的活動。但是到目前為止,還沒有一種可以明確地表達Cookie 使用意圖的方法。你的 promo_shown Cookie 應該只能在第一方上下文中被髮送,但是現在以巢狀視窗的形式在第三方上下文中還能保持會話狀態。

使用 SameSite 屬性明確 Cookie 的使用

SameSite  屬性的引入(在 RFC6265bis 中定義)允許你設定 Cookie 是否限制在第一方(即同站)上下文。充分理解“網站”的含義對理解同站限制很重要。網站由域名字尾和字尾前的那個部分組合而成。例如,域名 www.web.dev 是 web.dev 網站的一部分(譯註:這裡的域名字尾是 dev,它跟前面的 web 的組合“web.dev”被標識為一個網站,而 www.web.dev 這個子域被看做是網站的一部分)。

關鍵術語:

如果使用者在 www.web.dev 這個地址下,請求一張來自 static.web.dev 地址的圖片,那麼這被看做是 同站(same-site) 請求(譯註:www.web.devstatic.web.dev 看成是 web.dev 的一部分)。

在 publicsuffix.org 這個網站中定義了 公共字尾類表(public suffix list),這個列表中包含的域名字尾不是隻有像 .com 這樣的頂級域,還包含像 github.io 這樣的服務商字尾,也就是說 project.github.io 和 my-project.github.io 被看做是兩個不同的網站。

關鍵術語:

如果使用者在 your-project.github.io 這個地址下,請求一張來自 my-project.github.io 地址的圖片,那麼這被看做是 跨站(cross-site) 請求。

SameSite 屬性提供了三種控制 Cookie 行為的方式。你也可以選擇不指定這個屬性,或使用 StrictLax 值來限制 Cookie 只在同站請求上傳輸。

如果 SameSite 設定成 Strict,那麼 Cookie 只會在第一方上下文中傳輸。對使用者而言,除非請求網址跟瀏覽器位址列地址匹配,否則不會傳送 Cookie。如果 promo_shown Cookie 是這樣設定的:

Set-Cookie: promo_shown=1; SameSite=Strict
複製程式碼

當使用者是在你的網站上時,Cookie 會按預期跟隨請求一起傳送。但如果你是從另一個網站或者通過朋友傳送的電子郵件連結訪問你的網站的,那麼在初始請求中,將不會傳送 Cookie。當想 Cookie 資訊會關係到與更改密碼或購買這類相關功能時,這樣限制是很合適的,但對 promo_shown 這個欄位來說,過於嚴格了。

那就是為什麼有 SameSite=Lax 的原因,如果你的網站是通過頂級導航訪問(top-level navigations)的,那麼就會傳送 Cookie。讓我們從上面重新來看下上面的貓咪文章例子——另一個站點引用了你網站的內容,直接引用你貓咪照片,並提供指向原始文章的連結。

<p>Look at this amazing cat!</p>
<img src="https://blog.example/blog/img/amazing-cat.png" />
<p>Read the <a href="https://blog.example/blog/cat.html">article</a>.</p>
複製程式碼

Cookie 已設定為:

Set-Cookie: promo_shown=1; SameSite=Lax
複製程式碼

那麼讀者在他人的部落格上瀏覽 amazing-cat.png 圖片時不會傳送 Cookie。 但是,如果讀者是通過連結訪問你部落格的 cat.html 頁面時,那麼請求中會包含 Cookie。這使得 Lax 成為影響網站顯示的不錯選擇,而 Strict 則在發生與使用者相關的操作時比較有用。

小心:

StrictLax 都不是網站安全的完整解決方案。Cookie 是作為使用者請求的一部分傳送的,你應該將它與使用者輸入一樣對待。這表示需要對輸入執行消毒(sanitizing)和驗證。不要使用 Cookie 來儲存伺服器端的保密資料。

最後還有一個選項是不指定值,這在以前是一種隱式的宣告、在所有上下文中傳送都傳送 Cookie。在 RFC6265bis 的最新草案中,通過引入一個新的值 SameSite=None 明確了這一點。這表示你可以使用 None 來明確指明支援在第三方上下文中傳送 Cookie。

image.png

將 Cookie 的上下文明確標記為 NoneLaxStrict

如果你是第三方服務提供商,為別的網站提供諸如元件(widgets)、嵌入式內容、會員程式、廣告或跨多個站點的登入服務,那麼你就應該使用 None 以明確你的意圖。

在沒有 SameSite 的情況下預設行為的修改

儘管 SameSite 屬性得到了廣泛支援,但不幸的是,它還沒有被開發著廣泛採用。開放的到處傳送預設 Cookie 的行為在所有用例中都能使用,但使使用者容易受到 CSRF 攻擊和意外資訊洩漏的影響。為了鼓勵開發者表明其意圖併為使用者提供更安全的體驗,IETF 在提案 漸進式更好的 Cookie 提出了兩個關鍵修改:

  • 未設定 SameSite 屬性的 Cookie 將被視為 SameSite=Lax
  • SameSite=None 的 Cookie 必須指定為 Secure,表示請求只能在安全的上下文中發起。

Chrome 自 80 版本起就實現了這些行為。Firefox 則從 69 版本起就支援對該行為的測試,並會在將來成為預設行為。要在 Firefox 中測試,需要開啟 about:config 並設定 network.cookie.sameSite.laxByDefaulttrueEdge 也計劃更改它的預設行為。

預設 SameSite=Lax

未設定屬性

Set-Cookie: promo_shown=1
複製程式碼

如果傳送 Cookie 時未指定 Samesite 屬性……

預設行為

Set-Cookie: promo_shown=1; SameSite=Lax
複製程式碼

瀏覽器預設會為 Cookie 指定 SameSite=Lax

雖然這是保證應用較安全的預設值,但理想情況下,你都應該明確設定的 SameSite 屬性,而不要依賴瀏覽器的預設設定。這能明確表達你的 Cookie 使用意圖,並提高了跨瀏覽器的一致性體驗。

小心:

與預設的 SameSite=Lax 相比,Chrome 所應用的預設行為要寬鬆一些,它允許某些 Cookie 在頂級 POST 請求上傳送。你可以在 blink-dev 公告中 看到確切的詳細資訊。這只是暫時的緩解措施,你仍然需要使用 SameSite=None; Secure 修復跨站點 Cookie。

SameSite=None 必須是 Secure

拒絕

Set-Cookie: widget_session=abc123; SameSite=None
複製程式碼

如此沒有指定 Secure 的 Cookie 會被拒絕

接受

Set-Cookie: widget_session=abc123; SameSite=None; Secure
複製程式碼

必須要保證 SameSite=None 與 Secure 一起設定

可以在 Chrome 76 中輸入 chrome://flags/#cookies-without-same-site-must-be-secure 或者在 Firefox 69 的位址列輸入 about:config,設定 network.cookie.sameSite.noneRequiresSecure 啟用該行為。

此功能會在設定新的 Cookie 時應用此功能,主動重新整理現有的 Cookie,即使這些 Cookie 的有效期還沒到。

如果你的網站依賴於第三方服務,那麼還應該向服務商商諮詢檢查下是否更新了服務,或者更新你的依賴項或程式碼片段,確保你的網站採用了新的行為。

這兩個更改都是向後相容的,這是指與那些已正確實現 SameSite 屬性的先前版本的或根本就不支援該屬性的瀏覽器。通過將這些更改應用於 Cookie,你可以明確指出其預期用途,而不是依賴瀏覽器的預設行為。同樣,任何尚未識別 SameSite=None 的客戶端都應該忽略它,作為跟未設定時一樣對待。

許多舊版本的瀏覽器(包括 Chrome、Safari 和 UC 瀏覽器)與新的 None 屬性不相容,並且可能會忽略或限制 Cookie。此行為在當前版本中都已修復,但是你應該檢查流量以確定受此影響的使用者比例。你可以在 Chromium 網站上看到 已知的不相容客戶端列表

SameSite Cookie 食譜

有關更新 Cookie 的準確詳細資訊、以便成功處理對SameSite=None 的這些更改以及瀏覽器行為的差異,請轉到後續文章 《SameSite Cookie 食譜》 中檢視。

在此致謝 ily Chen、Malte Ubl、Mike West、Rob Dodson、Tom Steiner 和 Vivek Sekhar 的貢獻和反饋。

使用的 Hero 照片來自 Unsplash 上的 Pille-Riin Priske

(正文完)


廣告時間(長期有效)

我有一位好朋友開了一間貓舍,在此幫她宣傳一下。現在貓舍裡養的都是布偶貓。如果你也是個愛貓人士並且有需要的話,不妨掃一掃她的【閒魚】二維碼。不買也不要緊,看看也行。

[譯] 理解 Cookie 的 SameSite 屬性

(完)

相關文章