自Chrome 51版本開始,瀏覽器的 Cookies 新增了一個SameSite
屬性,用來防止 CSRF 攻擊和信息泄漏,更多信息參考chrome Feature: 'SameSite' cookie attribute。
簡單回顧什么是CSRF攻擊
Cookies
往往用來存儲用戶的身份信息,惡意網站通過設法偽造帶有正確Cookies
進行 HTTP 請求,這就是 CSRF 攻擊。
舉例來說,用戶登陸了銀行網站your-bank.com
,銀行服務器發來了一個 Cookie。
Set-Cookie: session_id=abc123;
用戶后來又訪問了惡意網站malicious-site.com
,惡意網站總是想方設法讓你在惡意站點發送一個表單請求。
手段:中獎填寫聯系信息、透明的form表單提交按鈕、附加在誘惑圖片上的超鏈接
<form action="your-bank.com/transfer" method="POST">
...
</form>
用戶一旦被誘騙發送這個表單,銀行網站就會收到帶有正確 Cookie 的請求。為了防止CSRF攻擊,銀行網站表單會設置一個隱藏域,表單提交時一起帶上一個隨機token至服務器,告訴服務器這是真實請求。
<form action="your-bank.com/transfer" method="POST">
<input type="hidden" name="token" value="dad3weg34">
...
</form>
之所以被CSRF攻擊,是因為惡意網站誘導你在其頁面上發送了第三方cookie(此時的銀行網站cookie為第三方cookie),它除了用於 CSRF 攻擊,還可以用於用戶追蹤。
第三方是一種相對概念,規定假定你正在訪問的站點為第一方站點,則瀏覽器為第二方,其他網站就是第三方站點,第三方站點誘導你發送第一方站點的cookie時,我們就說此時的cookie為第三方cookie。
比如,Facebook 在第三方網站插入一張看不見的圖片。
<img src="facebook.com" style="visibility:hidden;">
瀏覽器加載上面代碼時,就會向 Facebook 發出帶有 Cookie 的請求,從而 Facebook 就會知道你是誰,訪問了什么網站。
SameSite
cookies機制一直被認為是不安全的,隨着技術的更新,界內一直在完善cookies的安全機制,SameSite
屬性是谷歌瀏覽器為完善cookies安全機制出的特性之一。
Cookie 的SameSite
屬性用來限制第三方 Cookie的行為。
它可以設置三個值。
- Strict
- Lax
- None
Strict
Strict
最為嚴格,完全禁止第三方 Cookie,當當前站點與請求目標站點是跨站
關系時,總是不會發送 Cookie。換言之,只有當前站點 與請求目標站點是同站
關系時,才會帶上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
這個規則過於嚴格,可能造成非常不好的用戶體驗。
舉例說明:
假定你當前所處站點地址為https://obmq.com/index.html
,該站需要通過XHR請求獲取某天氣站點的未來7天的天氣信息https://weather-forecast.org/api/weather?future=7
,該接口要求必須攜帶cookieSet-Cookie: vip=true; Path=/; HttpOnly; SameSite=Strict;
,這種情況下,你無論如何都無法在https://obmq.com
站點下發送這個XHR請求時還能攜帶上這個cookie,換句話說,你發送的接口請求的cookie請求頭一定不會有vip=true
,即使你現在已經是該天氣網站的vip。
None
None
在Chrome 85 版本之前是SameSite
的默認設置值,即Set-Cookie: key=value; SameSite=None
等於Set-Cookie: key=value
。
在Chrome 85 版本之前,顯示設置SameSite=None
不需要設置Secure
屬性,詳細參見:Reject insecure SameSite=None cookies
在Chrome 85 版本以后,站點選擇顯式關閉SameSite
屬性時,在將其值設為None
的同時。必須同時設置Secure
屬性(表示Cookie 只能通過 HTTPS 協議發送),否則無效。
下面的設置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
下面的設置無效。
Set-Cookie: widget_session=abc123; SameSite=None
Lax
Chrome 在85版本后將Lax
設為SameSite
的默認值,即Set-Cookie: key=value; SameSite=Lax
等於Set-Cookie: key=value
,詳細參見:Cookies default to SameSite=Lax。
Lax
規則比較寬松,大多數情況也不發送第三方 Cookie,但是導航到目標站點的 Get 請求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
導航到目標站點的 GET 請求,只包括三種情況:鏈接,預加載請求,GET 表單。詳見下表。
請求類型 | 示例 | SameSite=None;Secure | Lax |
---|---|---|---|
鏈接 | <a href="..."></a> | 發送 Cookie | 發送 Cookie |
預加載 | <link rel="prerender" href="..."/> | 發送 Cookie | 發送 Cookie |
GET 表單 | <form method="GET" action="..."> | 發送 Cookie | 發送 Cookie |
POST 表單 | <form method="POST" action="..."> | 發送 Cookie | 不發送 |
iframe | <iframe src="..."></iframe> | 發送 Cookie | 不發送 |
xhr/fetch | $.get("...") | 發送 Cookie | 不發送 |
Image | <img src="..."> | 發送 Cookie | 不發送 |
Script | <script src="..."> | 發送Cookie | 不發送 |
設置了Strict
或Lax
以后,基本就杜絕了 CSRF 攻擊。當然,前提是用戶瀏覽器支持 SameSite
屬性。
Schemeful Same-Site
自Chrome 86版本開始,考慮到不安全的http://
協議仍然為網絡攻擊者提供了篡改cookie的機會,然后將這些cookie用於站點安全的https://
。谷歌瀏覽器修改了cookie的Same Site
的定義,將在相同域名的安全(https://)協議和不安全(http://)協議作為是否跨站
的判斷因素之一。詳情參見Feature: Schemeful same-site
如果您的站點全面升級到https協議,那么下面的內容不適合您;如果您的站點是https協議和http協議混存,那么您需要關注。
常見的 "cross-scheme" Cookies攜帶情況
超鏈接
當Schemeful Same-Site
禁止時,從http:// site.example鏈接到
https😕/site.example時,即使SameSite=Strict
依然會攜帶上cookie。
當Schemeful Same-Site
啟用時,從http:// site.example鏈接到
https😕/site.example時,SameSite=Strict
的cookies會被鎖定,其他cookies攜帶的表現如下圖和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict |
⛔ Blocked | ⛔ Blocked |
SameSite=Lax |
✓ Allowed | ✓ Allowed |
SameSite=None;Secure |
✓ Allowed | ⛔ Blocked |
加載子資源
加載子資源的方式包括images
, iframes
, 和XHR or Fetch
的網絡請求。
加載子資源分為http加載https子資源
和https加載子http資源
,攜帶cookies的表現如下圖和下表:
當Schemeful Same-Site
禁止時,加載子資源時,SameSite=Strict
或者SameSite=Lax
的cookie會被攜帶上。
當Schemeful Same-Site
啟用時,加載子資源時,SameSite=Strict
或者SameSite=Lax
的cookies會被鎖定,其他cookies攜帶的表現如下圖和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict |
⛔ Blocked | ⛔ Blocked |
SameSite=Lax |
⛔ Blocked | ⛔ Blocked |
SameSite=None;Secure |
✓ Allowed | ⛔ Blocked |
POST 表單
當Schemeful Same-Site
禁止時,發送POST表單時,SameSite=Strict
或者SameSite=Lax
的cookie會被攜帶上。
當Schemeful Same-Site
啟用時,發送POST表單時,只有SameSite=None
的cookies會被攜帶,其他cookies攜帶的表現如下圖和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict |
⛔ Blocked | ⛔ Blocked |
SameSite=Lax |
⛔ Blocked | ⛔ Blocked |
SameSite=None;Secure |
✓ Allowed | ⛔ Blocked |
對WebSockets的影響?
如果WebSocket連接與頁面的安全性相同,則仍將被視為同站。
https://
連接wss://
被視為同站;http://
連接ws://
被視為同站,否則視為跨站。詳細如下:
Same-site:
wss://
從https://
連接ws://
從http://
連接
Cross-site:
wss://
從http://
連接ws://
從https://
連接
最佳實踐
依然使用token機制防止CSRF攻擊
設置了SameSite屬性值為Strict
或Lax
以后,基本杜絕了 CSRF 攻擊。但是SameSite是Cookies屬性之一,所以存在以下諸多限制:
-
要求瀏覽器必須兼容Cookies的SameSite屬性
-
要求客戶端應用必須支持Cookies機制,比如APP和微信小程序並不支持Cookies
限制如上所列,但不限於所列。
因為SameSite屬性存在以上限制,所以需要服務器端依然采用token機制來防止CSRF攻擊。
顯示指定SameSite屬性值為Lax
或Strict
總應該設置一個顯式的SameSite
屬性,而不是依賴瀏覽器為您應用的默認設置。這使您對Cookie的使用意圖更加明確,並提高了跨瀏覽器獲得一致體驗的機會。
不兼容的客戶端的處理
對於SameSite
屬性的兼容性在不同瀏覽器和相同瀏覽器的不同版本之間是不同的,可以參考chromium.org上的更新頁面的已知的不兼容客戶端以了解當前已知的問題,但是無法確定是否詳盡無遺。 盡管這不是理想的選擇,但可以在此過渡階段中采用一些解決方法。
方式一:同時設置兼容SameSite屬性的客戶端和不兼容SameSite屬性的客戶端
// ~ 同時設置兼容SameSite屬性的客戶端和不兼容SameSite屬性的客戶端
// ~ 對於兼容SameSite屬性的客戶端,顯示指定SameSite屬性值以明確使用意圖
Set-cookie: 3pcookie=value; SameSite=None; Secure
// ~ 對於不兼容SameSite屬性的客戶端,不設置SameSite屬性
Set-cookie: 3pcookie-legacy=value; Secure
下面的示例顯示了如何使用Express框架及其cookie-parser中間件在Node.js中執行此操作。
const express = require('express');
const cp = require('cookie-parser');
const app = express();
app.use(cp());
app.get('/set', (req, res) => {
// Set the new style cookie
res.cookie('3pcookie', 'value', { sameSite: 'none', secure: true });
// And set the same value in the legacy cookie
res.cookie('3pcookie-legacy', 'value', { secure: true });
res.end();
});
app.get('/', (req, res) => {
let cookieVal = null;
if (req.cookies['3pcookie']) {
// check the new style cookie first
cookieVal = req.cookies['3pcookie'];
} else if (req.cookies['3pcookie-legacy']) {
// otherwise fall back to the legacy cookie
cookieVal = req.cookies['3pcookie-legacy'];
}
res.end();
});
app.listen(process.env.PORT);
方式二:判斷客戶端的user-agent
在發送Set-Cookie
相應頭時,您可以選擇通過user-agent
字符串檢測客戶端。請參閱不兼容客戶端的列表,建議您找一個工具庫來處理user-agent
,因為您很可能不想自己編寫這些正則表達式。
這種方法的好處在於,它只需要在設置cookie時進行一次更改即可。 但是,此處必須指出是user-agent
嗅探本身是不可靠的,並且可能無法捕獲所有受影響的用戶。
無論選擇哪種方式,建議確保有一種記錄低版本客戶端比例的方法。 一旦比例下降到網站可接受的閾值以下,請確保您有提醒或警報以刪除此替代方法。
谷歌計划推出"Privacy Sandbox"
谷歌計划完全禁止第三方cookie,畢竟cookie真的不是很安全,詳細參考this
參考鏈接
文章同步發布在各大主流知識共享平台,所以設github
為統一的反饋區
疑問、討論、問題反饋:https://github.com/weixsun/discussion
關注微信公眾號 obmq 及時了解最新動態