Cookie SameSite屬性介紹及其在ASP.NET項目中的應用

  • 2020 年 3 月 29 日
  • 筆記

一、Cookie SameSite屬性介紹

就像大家已經知道的,一旦設置Cookie之後,在Cookie失效之前瀏覽器會一直將這個Cookie在後續所有的請求中都傳回到Server端。我們的系統會利用Cookie這個特性做很多事情,但通常我們會在Cookie中存放加密的用戶身份,在Server端根據此身份檢驗用戶是否有權限進行相應操作。

發送Cookie時,以往瀏覽器並不檢測當前地址欄上的域(Domain)是不是和這個Cookie所屬的域是否相同。惡意用戶會利用這個問題巧妙設計一個站點,誘導用戶點擊從而造成跨站點請求偽造攻擊(CSRF)。

為了解決這個問題,國際互聯網工程任務組(IETF)提出了一個SameSite的草稿標準,Chrome 51開始支持此功能,但從Chrome 80 Stable版本開始啟用一個較嚴格(Lax)的默認設置。

二、什麼是跨站點請求偽造攻擊(Cross-Site Request Forgery Attack,CSRF)

CSRF攻擊簡單而言就是惡意用戶通過巧妙偽造請求從而盜用合法用戶的身份進行惡意操作。

比如你開發了一個非常厲害的系統,系統中某些操作只有特定的人登錄之後才有權限使用:

yourdomain.com/snap

[Authorize("Thanos")]  [HttpPost]  public ActionResult Snap()  {      ///dangerous, will destroy the world.  }  

因為系統要檢驗身份和權限,除非惡意用戶能破解登錄系統以Thanos身份登錄,否則是沒有辦法調用這個方法的。

但是惡意用戶可以偽造一個像下面這樣的頁面,惡意用戶通過發郵件或者通過跨站點腳本攻擊(XSS)等方式誘導具有權限的用戶點擊頁面上的某些Button。如果具有權限的用戶剛好已經登錄,一旦點擊按鈕,系統則會以這個用戶的身份觸發上面危險的操作Snap()。

malicioususer.com/fancypage

...  <form action="yourdomain.com/snap">      <input type="Submit" value="This is cool, click me"/>  </form>  ...  

當然,微軟 ASP.NET是通過AntiForgeryToken來解決這個問題,不過這個不是這篇blog討論的主題。

三、Cookie的SameSite屬性

為了解決上面到的Cookie的安全問題,Chrome從版本51增加了一個新的Cookie屬性SameSite, 以控制Cookie是否能在跨站點的情況下傳送。

Cookie所屬的域名如果和瀏覽器地址欄中的域名不一致,則認為是跨站點。另外,當你的站點被ifame嵌在第三方站點時也被認為是跨站點。

這個屬性有三個屬性值:

  1. None

    如果你需要在任意跨站點情況下都使用某個Cookie,則需要將這個Cookie的SameSite設置為None. 但這裡需要注意的是一定要同時設置Cookie的Secure,也就是需要使用https訪問時才能關閉SameSite功能. 如果沒有標明為secure, Chrome 80及以上會拒絕設置這個Cookie。

    set-cookie: samesite=test; path=/; secure; SameSite=None  
  2. Strict

    故名思義,這是嚴格模式,就是在任何情況下都不允許跨站點發送Cookie。

    這個設置顯然是可以解決上面所提到的CSRF問題。因為當訪問 malicioususer.com/fancypage 頁面時,當前域是 malicioususer.com, 但user點擊button提交時的action是指向另外一個域 yourdomain.com,這是兩個不同的域,瀏覽器將不回傳yourdomain.com下面的Cookie。這會極大的提高我們系統的安全性。

    但這個嚴格模式也限制了一些被認為是安全的鏈接操作,比如:

    1. 你先登錄了公司HR系統,假設該系統將所有Cookie的SameSite都設置為strict.
    2. 你用Web郵件系統收到了要求你到HR系統做審批操作的郵件,這封郵件帶了一個link,直接鏈接到HR系統中審批的頁面;
    3. 你點擊這個link,但因為Cookie被設置為Strict模式,當到達審批頁面時,HR系統沒有收到任何Cookie,這時會認為你沒有登錄,而直接跳轉到登錄頁面。在要求不是非常嚴格的情形下,可以認為這不是我們所期望的行為。因為只是跳到鏈接指向的頁面並不是像POST操作修改數據。這需要通過下面的Lax屬性解決這個問題。
  3. Lax

    Lax是比Strict稍寬鬆的模式,如果我們要允許跨站點鏈接傳Cookie或FORM用GET Method提交時跨站點傳Cookie, 則可以將這些Cookie的SameSite設置為Lax. Lax在Chrome 80成為默認設置,Lax既防止了CSRF也確保了正常的跨站點鏈接,是適合大多數站點的,可以解決上面HR系統安全中提到問題。

    如果你的站點需要被iframe嵌套在第三方站點,這時你還是需要將Cookie設置為None。

    這裡也想到一點是,如果你的MVC Action只期望接受POST方法,那麼一定要加上HttpPost Attribute,以避免造成意外的安全問題。

四、瀏覽器兼容性

如下圖示目前主流瀏覽器都已經支持SameSite,雖然 IE 11不支持,但我測試之後發現這個Cookie本身還是沒有丟失,只是缺失了安全保護功能。
https://developer.mozilla.org/en-US/docs/Web/HTTP/headers/Set-Cookie#Browser_compatibility
Browser Compabibility

五、如何修改ASP.NET程序

下面總結的步驟是適用於基於ASP.NET開發的系統。微軟官方白皮書對這些屬性設置做了詳細的說明,也可以參考官方白皮書

  1. 安裝 .NET Framework 4.7.2 或4.8, 需要安裝在開發電腦和服務器上。

  2. 安裝 Windows 2019/11/19累積更新補丁,請見KB Articles that support SameSite in .NET Framework,需要安裝在開發電腦和服務器上。

  3. 在Chrome地址欄輸入: chrome://flags/, 將下面兩項設置為Enabled。開啟這兩項設置是因為不是所有的Chrome都默認啟用了這兩項設置,Chrome只是在逐漸將這兩項開啟到Chrome的user. 所以開發時為了重現問題,最好是顯式開啟。
    chrome://flags/#same-site-by-default-cookies
    chrome://flags/#cookies-without-same-site-must-be-secure
    Enable Chrome SameSite explicitly

  4. 修改項目文件屬性, Target framework 4.7.2 或4.8。
    target .net 4.7.2 or above

  5. 根據需要修改web.config對Cookie的SameSite設置。

    <configuration>      <system.web>          <httpCookies sameSite="[Strict|Lax|None|Unspecified]" requireSSL="[true|false]" />          <anonymousIdentification cookieRequireSSL="false" /> <!-- No config attribute for SameSite -->          <authentication>              <forms cookieSameSite="Lax" requireSSL="false" />          </authentication>          <sessionState cookieSameSite="Lax" /> <!-- No config attribute for Secure -->          <roleManager cookieRequireSSL="false" /> <!-- No config attribute for SameSite -->      <system.web>  <configuration>  

    修改說明:

    • httpCookies節點中的sameSite設置會影響系統中所有未指定sameSite Cookie和的值, 但不覆蓋forms/sessionState中設置的SameSite屬性。

    • forms authentication的sessionState的默認Lax模式應該能滿足常規需要, 並且保證網站的安全.

    • 確實需要接受跨站點Cookie, 比如你的網站會嵌套在第三方網站的iframe裏面,則需要將相關的Cookie 的SameSite改為None。需要注意的是為None的時候必須要將requireSSL改為true:

      <configuration>      <system.web>          <httpCookies sameSite="None" requireSSL="true" />          <authentication>              <forms cookieSameSite="None" requireSSL="true" />          </authentication>          <sessionState cookieSameSite="None" />          <roleManager cookieRequireSSL="false" />      <system.web>  <configuration>  
    • Cookie的SameSite都設置為None之後,需要防範CSRF. ,比如使用AntiForgeryToken

  6. 如果某些Cookie需要使用與web.config中配置的不同SameSite屬性,只需要在設置Cookie的時候明確指定其值.

         var cookie = new HttpCookie("samesite", "test");       cookie.SameSite = SameSiteMode.None;       cookie.Secure = true;       Response.Cookies.Add(cookie);  
  7. 因為有些老的瀏覽器並不支持SameSite這個屬性,直接輸出這個屬性會造成老的瀏覽器忽略這個Cookie而造成Cookie丟失,請見已知的兼容問題. 如果確實需要支持這些老的瀏覽器,則需要根據user agent來檢測瀏覽器,對於不支持SameSite的瀏覽器,我們需要將SameSite設置為(SameSiteMode)(-1).

    private void CheckSameSite(HttpContext httpContext, HttpCookie cookie)  {      if (cookie.SameSite == SameSiteMode.None)      {          var userAgent = httpContext.Request.UserAgent;          if (BrowserDetection.DisallowsSameSiteNone(userAgent))          {              cookie.SameSite = (SameSiteMode)(-1);          }      }  }  

    BrowserDetection.DisallowsSameSiteNone()這個方法請見SampleSiteSupport.cs.

對於ASP.NET Core應用,微軟也提供了詳細的解決方案

六、如何排查SameSite問題

SameSite默認為Lax已經從Chrome 80 Stable正式開始灰度啟用,如果一個Cookie SameSite未指定,將會被默認為Lax,這可能會造成網站在某些情況下不能正常工作。
Chrome Developer Tools專門為SameSite問題提供了一個檢測工具,在Network tab下有一個選項"Only show requests with SameSite issues". 找到有問題的request之後,可以在Response Headers下面找到哪個Cookie有問題。如下圖示,因為我設置了SameSite為None,但沒有設置Secure,所以以Chrome會拒絕這個Cookie.

find which request and then cookie has the SameSite issues

set-cookie: samesite=test; path=/; SameSite=None  

在調試的時候如果有些跳轉操作,為了看到跳轉前後的請求記錄,可以勾中Preserve log

七、參考

MDN web docs: Set-Cookie

Chrome SameSite Updates

Cookies default to SameSite=Lax

Reject insecure SameSite=None cookies

Work with SameSite cookies in ASP.NET

Work with SameSite cookies in ASP.NET Core

作者:吳秀祥
2020/3/28