SameSite Cookie,防止 CSRF 攻击
因?yàn)?HTTP 協(xié)議是無狀態(tài)的,所以很久以前的網(wǎng)站是沒有登錄這個(gè)概念的,直到網(wǎng)景發(fā)明 cookie 以后,網(wǎng)站才開始利用 cookie 記錄用戶的登錄狀態(tài)。cookie 是個(gè)好東西,但它很不安全,其中一個(gè)原因是因?yàn)?cookie 最初被設(shè)計(jì)成了允許在第三方網(wǎng)站發(fā)起的請(qǐng)求中攜帶,CSRF 攻擊就是利用了 cookie 的這一“弱點(diǎn)”,如果你不了解 CSRF,請(qǐng)移步別的地方學(xué)習(xí)一下再來。
當(dāng)我們?cè)跒g覽器中打開 a.com 站點(diǎn)下的一個(gè)網(wǎng)頁后,這個(gè)頁面后續(xù)可以發(fā)起其它的 HTTP 請(qǐng)求,根據(jù)請(qǐng)求附帶的表現(xiàn)不同,這些請(qǐng)求可以分為兩大類:
1. 異步請(qǐng)求(不會(huì)改變當(dāng)前頁面,也不會(huì)打開新頁面),比如通過 <script>、<link>、<img>、<iframe> 等標(biāo)簽發(fā)起的請(qǐng)求,還有通過各種發(fā)送 HTTP 請(qǐng)求的 DOM API(XHR,fetch,sendBeacon)發(fā)起的請(qǐng)求。
2. 同步請(qǐng)求(可能改變當(dāng)前頁面,也可能打開新頁面),比如通過對(duì) <a> 的點(diǎn)擊,對(duì) <form> 的提交,還有改變 location.href,調(diào)用 window.open() 等方式產(chǎn)生的請(qǐng)求。
上面說的同步和異步并不是正式術(shù)語,只是我個(gè)人的一種區(qū)分方式。
這些由當(dāng)前頁面發(fā)起的請(qǐng)求的 URL 不一定也是 a.com 上的,可能有 b.com 的,也可能有 c.com 的。我們把發(fā)送給 a.com 上的請(qǐng)求叫做第一方請(qǐng)求(first-party request),發(fā)送給 b.com 和 c.com 等的請(qǐng)求叫做第三方請(qǐng)求(third-party request),第三方請(qǐng)求和第一方請(qǐng)求一樣,都會(huì)帶上各自域名下的 cookie,所以就有了第一方 cookie(first-party cookie)和第三方 cookie(third-party cookie)的區(qū)別。上面提到的 CSRF 攻擊,就是利用了第三方 cookie 。
防止 CSRF 攻擊的辦法已經(jīng)有?CSRF token 校驗(yàn)和 Referer 請(qǐng)求頭校驗(yàn)。為了從源頭上解決這個(gè)問題,Google 起草了一份草案來改進(jìn) HTTP 協(xié)議,那就是為 Set-Cookie 響應(yīng)頭新增 SameSite 屬性,它用來標(biāo)明這個(gè) cookie 是個(gè)“同站 cookie”,同站 cookie 只能作為第一方 cookie,不能作為第三方 cookie。SameSite 有兩個(gè)屬性值,分別是 Strict 和 Lax,下面分別講解:
SameSite=Strict:
嚴(yán)格模式,表明這個(gè) cookie 在任何情況下都不可能作為第三方 cookie,絕無例外。比如說假如 b.com 設(shè)置了如下 cookie:
Set-Cookie: foo=1; SameSite=Strict Set-Cookie: bar=2你在 a.com 下發(fā)起的對(duì) b.com 的任意請(qǐng)求中,foo 這個(gè) cookie 都不會(huì)被包含在 Cookie 請(qǐng)求頭中,但 bar 會(huì)。舉個(gè)實(shí)際的例子就是,假如淘寶網(wǎng)站用來識(shí)別用戶登錄與否的 cookie 被設(shè)置成了?SameSite=Strict,那么用戶從百度搜索頁面甚至天貓頁面的鏈接點(diǎn)擊進(jìn)入淘寶后,淘寶都不會(huì)是登錄狀態(tài),因?yàn)樘詫毜姆?wù)器不會(huì)接受到那個(gè) cookie,其它網(wǎng)站發(fā)起的對(duì)淘寶的任意請(qǐng)求都不會(huì)帶上那個(gè) cookie。
SameSite=Lax:
寬松模式,比 Strict 放寬了點(diǎn)限制:假如這個(gè)請(qǐng)求是我上面總結(jié)的那種同步請(qǐng)求(改變了當(dāng)前頁面或者打開了新頁面)且同時(shí)是個(gè) GET 請(qǐng)求(因?yàn)閺恼Z義上說 GET 是讀取操作,比 POST 更安全),則這個(gè) cookie 可以作為第三方 cookie。比如說假如 b.com 設(shè)置了如下 cookie:
Set-Cookie: foo=1; SameSite=Strict Set-Cookie: bar=2; SameSite=Lax Set-Cookie: baz=3當(dāng)用戶從 a.com 點(diǎn)擊鏈接進(jìn)入 b.com 時(shí),foo 這個(gè) cookie 不會(huì)被包含在 Cookie 請(qǐng)求頭中,但 bar 和 baz 會(huì),也就是說用戶在不同網(wǎng)站之間通過鏈接跳轉(zhuǎn)是不受影響了。但假如這個(gè)請(qǐng)求是從 a.com 發(fā)起的對(duì) b.com 的異步請(qǐng)求,或者頁面跳轉(zhuǎn)是通過表單的 post 提交觸發(fā)的,則 bar 也不會(huì)發(fā)送。
該用哪種模式?
該用哪種模式,要看你的需求。比如你的網(wǎng)站是一個(gè)少數(shù)人使用的后臺(tái)管理系統(tǒng),所有人的操作方式都是從自己瀏覽器的收藏夾里打開網(wǎng)址,那我看用 Strict 也無妨。如果你的網(wǎng)站是微博,用了 Strict 會(huì)這樣:有人在某個(gè)論壇里發(fā)了帖子“快看這個(gè)微博多搞笑 http://weibo.com/111111/aaaaaa”,結(jié)果下面人都回復(fù)“打不開啊”;如果你的網(wǎng)站是淘寶,用了 Strict 會(huì)這樣:某微商在微博上發(fā)了條消息“新百倫正品特賣5折起 https://item.taobao.com/item.htm?id=1111111”,結(jié)果點(diǎn)進(jìn)去顧客買不了,也就是說,這種超多用戶的、可能經(jīng)常需要用戶從別的網(wǎng)站點(diǎn)過來的網(wǎng)站,就不適合用 Strict 了。
假如你的網(wǎng)站有用 iframe 形式嵌在別的網(wǎng)站里的需求,那么連 Lax 你也不能用,因?yàn)?iframe 請(qǐng)求也是一種異步請(qǐng)求。或者假如別的網(wǎng)站有使用你的網(wǎng)站的 JSONP 接口,那么同樣 Lax 你也不能用,比如天貓就是通過淘寶的 JSONP 接口來判斷用戶是否登錄的。
有時(shí)安全性和靈活性就是矛盾的,需要取舍。
和瀏覽器的“禁用第三方 cookie”功能有什么區(qū)別??
主流瀏覽器都有禁用第三方 cookie 的功能,它和 SameSite 有什么區(qū)別?我能總結(jié) 3 點(diǎn):
1. 該功能是由用戶決定是否開啟的,是針對(duì)整個(gè)瀏覽器中所有 cookie 的,即便有些瀏覽器可以設(shè)置域名白名單,那最小單位也是域名;而 SameSite 是由網(wǎng)站決定是否開啟的,它針對(duì)的是某個(gè)網(wǎng)站下的單個(gè) cookie。
2. 該功能同時(shí)禁用第三方 cookie 的讀和寫,比如 a.com 發(fā)起了對(duì) b.com 的請(qǐng)求,這個(gè)請(qǐng)求完全不會(huì)有 Cookie 請(qǐng)求頭,同時(shí)假如這個(gè)請(qǐng)求的響應(yīng)頭里有 Set-Cookie: foo=1,foo 這個(gè) cookie 也不會(huì)被寫進(jìn)瀏覽器里;而 SameSite 只禁用讀,比如 b.com 在用戶瀏覽器下已經(jīng)寫入了個(gè) SameSite cookie foo,當(dāng) a.com 請(qǐng)求 b.com 時(shí),foo 肯定不會(huì)被發(fā)送過去,但 b.com 在這個(gè)請(qǐng)求的響應(yīng)里又返回了:?Set-Cookie: bar=1; SameSite=Strcit,這個(gè) bar 會(huì)成功寫入瀏覽器的 cookie 里。
3. 該功能不會(huì)把我上面說的那種同步請(qǐng)求(改變了當(dāng)前頁面或者打開了新頁面)算在第三方請(qǐng)求里,因此也不會(huì)攔截對(duì)應(yīng)的 cookie。
到底怎樣才算第三方請(qǐng)求?
我上面說的原話是:當(dāng)一個(gè)請(qǐng)求本身的 URL 和它的發(fā)起頁面的 URL 不屬于同一個(gè)站點(diǎn)時(shí),這個(gè)請(qǐng)求就算第三方請(qǐng)求。那么怎樣算是同一個(gè)站點(diǎn)?是我們經(jīng)常說的同源(same-origin)嗎,cross-origin 的兩個(gè)請(qǐng)求就不屬于同一個(gè)站點(diǎn)?顯然不是的,foo.a.com 和 bar.a.com 是不同源的,但很有可能是同一個(gè)站點(diǎn)的,a.com 和 a.com:8000 是不同源的,但它倆絕對(duì)是屬于同一個(gè)站點(diǎn)的,瀏覽器在判斷第三方請(qǐng)求時(shí)用的判斷邏輯并不是同源策略,而是用了?Public Suffix List 來判斷。
有些同學(xué)可能會(huì)這么想:一個(gè)域名可以用逗號(hào)分成多個(gè)字段,如果兩個(gè)域名的最后兩個(gè)字段都是相同的,那它們就是同一個(gè)站點(diǎn)的,比如 foo.a.com 和 bar.a.com 就是。但是 sina.com.cn 和 sohu.com.cn 也滿足這個(gè)條件啊,它們絕對(duì)不是同一個(gè)網(wǎng)站吧,那是不是說瀏覽器需要維護(hù)一份列表來記錄所有國(guó)家頒布的二級(jí)域名啊,但是不僅國(guó)家可以開放三級(jí)域名給不同的網(wǎng)站使用,普通的網(wǎng)站也可能會(huì),比如新浪就開放 *.sinaapp.com 三級(jí)域名注冊(cè),foo.sinaapp.com 和?bar.sinaapp.com 是兩個(gè)不同的網(wǎng)站,那 sinaapp.com?也應(yīng)該加入那個(gè)列表中,以及 github.io 等等。
Mozilla 很久之前就將自己維護(hù)的這個(gè)域名后綴列表放到了 github 上,起名為 Public Suffix List,里面不僅有 IANA 頒布的頂級(jí)域名,眾多二級(jí)域名,還有三級(jí)域名比如?compute.amazonaws.com,甚至四級(jí)域名比如?compute.amazonaws.com.cn,判斷兩個(gè) URL 是不是同一個(gè)網(wǎng)站的,只要判斷兩個(gè) URL 的域名的 public suffix(按能匹配到的最長(zhǎng)的算)以及它前面的那個(gè)字段(后面用 public suffix+1 指代)是否都相同,是的話就是同一個(gè)站點(diǎn)的,否則不是。比如 www.sina.com.cn?的?public suffix+1 是 sina.com.cn,www.sohu.com.cn?的?public suffix+1 是 sohu.com.cn, 兩者不一樣,所以不屬于同一個(gè)站點(diǎn);再比如?nanzhuang.taobao.com 的?public suffix+1 是 taobao.com,nvzhuang.taobao.com?的?public suffix+1 也是 taobao.com,那么它倆就是同一個(gè)站點(diǎn)的。
Public Suffix List 最初被 Firefox 用在限制 Set-Cookie 響應(yīng)頭的 Domain 屬性上的, Domain 不能設(shè)置成一個(gè)比自己網(wǎng)站的 public suffix+1?還高層級(jí)的域名,比如 foo.w3c.github.io 就不能設(shè)置?Set-Cookie: foo=1; Domain=github.io,最高只能設(shè)置成 Set-Cookie: bar=1; Domain=w3c.github.io,現(xiàn)在其它瀏覽器也都在用同樣的列表做同樣的限制。DOM API 里的 document.domain 后來也加上了這個(gè)限制。有些瀏覽器還用這個(gè)列表來高亮地址欄上的 URL 中的?public suffix+1 部分(Firefox 和 IE 有用,Chrome 是高亮了整個(gè)域名),此外瀏覽器們還用該列表干一些其它瑣事,比如將歷史網(wǎng)址按不同站點(diǎn)排列等等。
瀏覽器們會(huì)定期同步這份列表,比如 Chrome 是在每個(gè)正式版本發(fā)布之前同步一次。
后臺(tái)語言的支持程度
目前還沒有哪個(gè)后臺(tái)語言的 API 支持了 SameSite 屬性,比如 php 里的 setcookie 函數(shù),或者 java 里的?java.net.HttpCookie?類,如果你想使用 SameSite,需要使用更底層的 API 直接修改 Set-Cookie 響應(yīng)頭。Node.js 本來就沒有專門設(shè)置 cookie 的 API,只有通用的 setHeader 方法,不過 Node.js 的框架 Express 已經(jīng)支持了 SameSite。
使用 document.cookie 測(cè)試
如果覺得開 http 服務(wù)測(cè)試 SameSite cookie 比較麻煩的話,你也可以使用 document.cookie 來代替,比如 document.cookie="foo=1;SameSite=Strict",為 document.cookie 賦值和使用 Set-Cookie 響應(yīng)頭的效果幾乎一摸一樣,除了不能讀取和設(shè)置帶 HttpOnly 屬性的 cookie 以外。?
轉(zhuǎn)載于:https://www.cnblogs.com/defined/p/6286009.html
總結(jié)
以上是生活随笔為你收集整理的SameSite Cookie,防止 CSRF 攻击的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Rx 中预定义的 Subject
- 下一篇: 怎样通过WireShark抓到的包分析出