Cookie SameSite属性介绍及其在ASP.NET项目中的应用

一、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

...
...

当然,微软 ASP.NET是通过AntiForgeryToken来解决这个问题,不过这个不是这篇blog讨论的主题。

三、Cookie的SameSite属性

为了解决上面到的Cookie的安全问题,Chrome从版本51增加了一个新的Cookie属性SameSite, 以控制Cookie是否能在跨站点的情况下传送。

Cookie所属的域名如果和浏览器地址栏中的域名不一致,则认为是跨站点。另外,当你的站点被iframe嵌在第三方站点时也被认为是跨站点。

这个属性有三个属性值:

  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
Cookie SameSite属性介绍及其在ASP.NET项目中的应用_第1张图片

五、如何修改ASP.NET程序

下面总结的步骤是适用于基于ASP.NET开发的系统。微软官方白皮书对这些属性设置做了详细的说明,也可以参考官方白皮书。

  1. .NET Framework 4.7.2 或4.8才开始支持SameSite, 在HttpCookie增加了SameSite的属性。所以需要安装.NET Framework 4.7.2以上SDK, 并且需要安装开发电脑和服务器上。

  2. 安装 Windows 2019/11/19累积更新补丁,请见KB Articles that support SameSite in .NET Framework,也需要安装在开发电脑和服务器上。没有安装这个补丁之前,如果SameSite为None, .NET Framework并不输出这个属性到Broswer, 但Chrome 80及以后将未设置默认为Lax,因此造成不一致的行为,所以需要安装这个补丁以明确输出None。

  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
    Cookie SameSite属性介绍及其在ASP.NET项目中的应用_第2张图片

  4. 修改项目文件属性, Target framework 4.7.2 或4.8。
    Cookie SameSite属性介绍及其在ASP.NET项目中的应用_第3张图片

  5. 根据需要修改web.config对Cookie的SameSite设置。

    
        
            
             
            
                
            
             
             
        
    
    

    修改说明:

    • httpCookies节点中的sameSite设置会影响系统中所有未指定sameSite Cookie的值, 但不覆盖forms/sessionState中设置的SameSite属性。

    • forms authentication的sessionState的默认Lax模式应该能满足常规需要, 并且保证网站的安全.

    • 确实需要接受跨站点Cookie, 比如你的网站会嵌套在第三方网站的iframe里面,则需要将相关的Cookie 的SameSite改为None。需要注意的是为None的时候必须要将requireSSL改为true:

      
          
              
              
                  
              
              
              
          
      
      
    • 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.

Cookie SameSite属性介绍及其在ASP.NET项目中的应用_第4张图片

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

你可能感兴趣的:(Cookie SameSite属性介绍及其在ASP.NET项目中的应用)