Cookie 的 SameSite 属性
<p>Chrome 51 开始,浏览器的 Cookie 新增加了一个<code>SameSite</code>属性,用来防止 CSRF 攻击和用户追踪。</p>
<h2>一、CSRF 攻击是什么?</h2>
<p>Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击。</p>
<p>举例来说,用户登陆了银行网站<code>your-bank.com</code>,银行服务器发来了一个 Cookie。</p>
<blockquote>
<pre><code>Set-Cookie:id=a3fWa;</code></pre>
</blockquote>
<p>用户后来又访问了恶意网站<code>malicious.com</code>,上面有一个表单。</p>
<blockquote>
<pre><code><form action="your-bank.com/transfer" method="POST">
...
</form></code></pre>
</blockquote>
<p>用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,表单一般都带有一个随机 token,告诉服务器这是真实请求。</p>
<blockquote>
<pre><code><form action="your-bank.com/transfer" method="POST">
<input type="hidden" name="token" value="dad3weg34">
...
</form></code></pre>
</blockquote>
<p>这种第三方网站引导发出的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。</p>
<p>比如,Facebook 在第三方网站插入一张看不见的图片。</p>
<blockquote>
<pre><code><img src="facebook.com" style="visibility:hidden;"></code></pre>
</blockquote>
<p>浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。</p>
<h2>二、SameSite 属性</h2>
<p>Cookie 的<code>SameSite</code>属性用来限制第三方 Cookie,从而减少安全风险。</p>
<p>它可以设置三个值。</p>
<blockquote>
<ul>
<li>Strict</li>
<li>Lax</li>
<li>None</li>
</ul>
</blockquote>
<h3>2.1 Strict</h3>
<p><code>Strict</code>最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。</p>
<blockquote>
<pre><code>Set-Cookie: CookieName=CookieValue; SameSite=Strict;</code></pre>
</blockquote>
<p>这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。</p>
<h3>2.2 Lax</h3>
<p><code>Lax</code>规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。</p>
<blockquote>
<pre><code>Set-Cookie: CookieName=CookieValue; SameSite=Lax;</code></pre>
</blockquote>
<p>导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。</p>
<table>
<thead>
<tr>
<th>请求类型</th>
<th>示例</th>
<th>正常情况</th>
<th>Lax</th>
</tr>
</thead>
<tbody>
<tr>
<td>链接</td>
<td><code><a href="..."></a></code></td>
<td>发送 Cookie</td>
<td>发送 Cookie</td>
</tr>
<tr>
<td>预加载</td>
<td><code><link rel="prerender" href="..."/></code></td>
<td>发送 Cookie</td>
<td>发送 Cookie</td>
</tr>
<tr>
<td>GET 表单</td>
<td><code><form method="GET" action="..."></code></td>
<td>发送 Cookie</td>
<td>发送 Cookie</td>
</tr>
<tr>
<td>POST 表单</td>
<td><code><form method="POST" action="..."></code></td>
<td>发送 Cookie</td>
<td>不发送</td>
</tr>
<tr>
<td>iframe</td>
<td><code><iframe src="..."></iframe></code></td>
<td>发送 Cookie</td>
<td>不发送</td>
</tr>
<tr>
<td>AJAX</td>
<td><code>$.get("...")</code></td>
<td>发送 Cookie</td>
<td>不发送</td>
</tr>
<tr>
<td>Image</td>
<td><code><img src="..."></code></td>
<td>发送 Cookie</td>
<td>不发送</td>
</tr>
</tbody>
</table>
<p>设置了<code>Strict</code>或<code>Lax</code>以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。</p>
<h3>2.3 None</h3>
<p>Chrome 计划将<code>Lax</code>变为默认设置。这时,网站可以选择显式关闭<code>SameSite</code>属性,将其设为<code>None</code>。不过,前提是必须同时设置<code>Secure</code>属性(Cookie 只能通过 HTTPS 协议发送),否则无效。</p>
<p>下面的设置无效。</p>
<blockquote>
<pre><code>Set-Cookie: widget_session=abc123; SameSite=None</code></pre>
</blockquote>
<p>下面的设置有效。</p>
<blockquote>
<pre><code>Set-Cookie: widget_session=abc123; SameSite=None; Secure</code></pre>
</blockquote>