我遇过的最难的 Cookie 问题

GitHub 原文鏈結,繁體中文版)html

前言

几个礼拜前我在工做上碰到了一些跟 Cookie 有关的问题,在这以前,我本来想说:Cookie 不就那样嘛,就算有些属性不太熟悉,上网找一下资料就行了,哪有什麽跟 Cookie 有关的难题?git

然而事实证实我错了。我还真的碰到了一个让我解超久的 Cookie 问题。github

相信看到这边,不少人应该跃跃欲试了,那我就先来考一下你们:django

什麽情形下,Cookie 会写不进去?c#

像是语法错误那种显而易见的就不用说了,除此以外你可能会答说:写彻底不一样 domain 的 Cookie。例如说你的网页在 http://a.com 却硬要写 http://b.com 的 Cookie,这种情形固然写不进去。浏览器

或者,你可能会回答:不在 https 却想加上 Secure flag 的 Cookie。
没错,像是这种情形也会写不进去。安全

除了这些,你还能想到什麽吗?bash

若是想不太到,那就听我娓娓道来吧!cookie

悲剧的开始

在一个月前我写了一篇跟 CSRF 有关的文章(让咱们来谈谈 CSRF),正是由于工做上须要实做 CSRF 的防护,因此趁机研究了一下。简单来讲,就是要在 Cookie 设置一个 csrftokensession

但是那天我却发现,我怎麽写都写不进去。

个人测试网站的网址是:http://test.huli.com,拿来写 Cookie 的 script 是:

document.cookie = "csrftoken=11111111; expires=Wed, 29 Mar 2020 10:03:33 GMT; domain=.huli.com; path=/"
复制代码

我就只是想对.huli.com写一个名称是csrftoken的 Cookie。而我碰到的问题,就是怎麽写都写不进去。

这段语法彻底没有问题,我检查过好几遍了,但就是不知道为什麽写不进去。咱们开头讲的那几种 case 这边都彻底没碰到。这只是一个简单的 http 网站,并且是写本身 domain 的 Cookie,怎麽会写不进去?

刚开始碰到这情形,我还想说会不会是我电脑的灵异现象,在其余人的电脑上就行了,就暂时没有管它,直到有一天 PM 跟我说:「咦,这个页面怎麽坏了?」,我仔细检查后才发现是由于他也写不进去这个 Cookie,致使 server 没有收到 csrftoken 而验证失败。

好了,看来如今已经确认不是我电脑上的问题了,而是你们都会这样。但是,却有其余人是正常的。其余人均可以,但就只有我跟 PM 两我的不行。

幸亏见太小风小浪的我知道,每次碰到这种诡异的问题,先开无痕模式再说,至少能够知道你的浏览器不会被其余因素给干扰。打开无痕模式以后发现,能够了,能够设定 Cookie 了。在通常状况下不行设定,可是开无痕浏览模式却能够。

这就真的很奇怪了,到底为什麽不行呢?并且如果我把 Cookie 换了一个名字,叫作csrftoken2,就能够写入了!就惟独csrftoken这个名称不行,但是 Cookie 总不可能有保留字这种东西吧!就算真的有,csrftoken也绝对不会是保留字。

这一切都太诡异了,到底csrftoken这个名字有什麽问题?到底为什麽写不进去?

因而我就去拜了 Google 大神,用cookie 不能写cookie can not setunable set cookie等等的关键字去搜寻,却都一无所得,找到的答案都跟个人状况彻底不同。

我用 Chrome devtool 看了,明明http://test.huli.com就没有任何的 Cookie,怎麽会写不进去呢?

在经历过一阵乱找资料以后,我还稍微去翻了 cookie 的 rfc:HTTP State Management Mechanism,但仍是没有找到相关资料。

最后不知道哪来的灵感,我就去 Chrome 的设定那边检视全部 huli.com 的 Cookie,而且一个一个看过以后删掉。删完以后,就能够正常写入 Cookie 了。

仔细想一想其实还满合理的,毕竟无痕模式能够,就表明是之前作的一些事情会影响到写 Cookie 这件事,再经由删除 Cookie 就能够确认问题必定是出在其余有关的 Domain 身上,推测是其余 Domain 作了一些事情,才会形成 http://test.huli.com 没办法写入 Cookie。

后来我回想起刚刚删掉的那几个 Cookie,发现存在一个也叫作csrftoken的同名 cookie。

拨云见日

可贵让我找到了一点线索,固然要跟着这条线索继续查下去。

回想了一下,发现是另一个负责后台管理的网站叫作:https://admin.huli.com写的,由于是用 django 的关係,因此开启 CSRF 防御以后预设的 Cookie 名称就是csrftoken

仔细再用 Chrome devtool 看了一下,这个 Cookie 设置了Secure,Domain是 .admin.huli.com。看起来也没什麽异状。

然而,在拜访这个网站以后,我再试着去 http://test.huli.com,发现又没办法写入 Cookie 了,甚至本来的 Cookie 也离奇地消失了。

太棒了!看来我离真相愈来愈近了!

我把这个.admin.huli.com的同名 Cookie 删掉以后,去拜访我本身的http://test.huli.com,发现一切都正常。Cookie 能够正常写入。

看来答案很明显了,那就是:

只要.admin.huli.com的那个同名 Cookie 存在,http://test.huli.com就没办法对.huli.com写入同名的 Cookie。

解法其实到这边就很明显了,第一个是改一个 Cookie 名称,第二个是改一个 Domain。

有关于第二个解法,还记得咱们在 http://test.huli.com 是写入 .huli.com 这个 Domain 的 Cookie 吗?只要改为写入 .test.huli.com 这个 Domain,同样能够正常运做。

因此如果讲得更详细一点,这个写不进去 Cookie 的问题就发生在:

当有一个 Domain 为.admin.huli.com并设置成Secure的 Cookie 已经存在的时候,http://test.huli.com就没办法对.huli.com写入同名的 Cookie。

在大概确认问题之后,我就开始调整各个变因,看能不能查出究竟是哪个环节出了问题,最后我发现两个重点:

  1. 其实只有 Chrome 不能写,Safari, Firefox 均可以
  2. Secure 这个 flag 没有设置的话,就能够写

深刻追查

既然有了只有 Chrome 会发生这种情形的这个有力线索,就能够循着这条线继续追查下去,那怎麽追查呢?

没错,就是最简单直接的方法:去找 Chromium 的原始码!

之前看过不少文章都是查问题查一查最后查到 Source code 去,终于轮到我也有这一天了。但是 Chromium 的原始码这麽一大包,该如何找起呢?

因而我决定先 Google:chromium cookie,在第一笔搜寻结果发现了颇有帮助的资料:CookieMonster。这篇文章有详细说明了 Chromium 的 Cookie 机制是怎麽运做的,而且说明核心就是一个叫作 CookieMonster 的东西。

再来就能够直接去看 Source code 了,能够在 /net/cookies 找到 cookie_monster.cc

还记得刚刚发现的问题重点之一,推测是跟Secure这个 flag 有关,因此直接用 Secure 当关键字下去搜寻,能够在中间的部分发现一个 DeleteAnyEquivalentCookie 的 function,如下节录部分原始码,1146 行到 1173 行:

// If the cookie is being set from an insecure scheme, then if a cookie
// already exists with the same name and it is Secure, then the cookie
// should *not* be updated if they domain-match and ignoring the path
// attribute.
//
// See: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone
if (cc->IsSecure() && !source_url.SchemeIsCryptographic() &&
    ecc.IsEquivalentForSecureCookieMatching(*cc)) {
  skipped_secure_cookie = true;
  histogram_cookie_delete_equivalent_->Add(
      COOKIE_DELETE_EQUIVALENT_SKIPPING_SECURE);
  // If the cookie is equivalent to the new cookie and wouldn't have been
  // skipped for being HTTP-only, record that it is a skipped secure cookie
  // that would have been deleted otherwise.
  if (ecc.IsEquivalent(*cc)) {
    found_equivalent_cookie = true;
    if (!skip_httponly || !cc->IsHttpOnly()) {
      histogram_cookie_delete_equivalent_->Add(
          COOKIE_DELETE_EQUIVALENT_WOULD_HAVE_DELETED);
    }
  }
} 
复制代码

这边很贴心的帮你加上了注释,说是:

若是有个 cookie 是来自 insecure scheme,而且已经存在一个同名又设置为 Secure 又 domain-match 的 cookie 的话,这个 cookie 就不应被设置

虽然不太理解 domain-match 指的究竟是怎样才算 match,但看来咱们碰到的写不进去 Cookie 的问题就是在这一段发生的。并且还有贴心附上参考资料:tools.ietf.org/html/draft-… 标题为:「Deprecate modification of 'secure' cookies from non-secure origins」。

内容不长,很快就能够看完,如下节录其中一小段:

Section 8.5 and Section 8.6 of [RFC6265] spell out some of the
drawbacks of cookies' implementation: due to historical accident, non-secure origins can set cookies which will be delivered to secure origins in a manner indistinguishable from cookies set by that origin itself. This enables a number of attacks, which have been recently spelled out in some detail in [COOKIE-INTEGRITY]. 复制代码

附注中的参考资料是这个:Cookies Lack Integrity: Real-World Implications,裡面有附一段二十几分钟的影片,能够看一看,看完以后就会知道为什麽不能写入了。

若是你还没看,这边能够帮你们作一个总结。要知道为什麽刚开始那个 case 不能写入 Cookie,能够先想一想看若是能够写入,会发生什麽事情。

假如 http://test.huli.com 成功写入 .huli.comcsrftoken 这个 cookie 的话,对 http://test.huli.com 彷佛没什麽影响,就多带一个 Cookie 上去,看起来合情合理。

但是呢,却对 https://admin.huli.com 有些影响。

本来 .admin.huli.com 而且设置为 Secure 的 Cookie 仍是会在,但如今多了个 .huli.com 又是同名的 Cookie。当 https://admin.huli.com 送 request 的时候,就会把这两个 Cookie 一併带上去。因此 Server 收到的时候可能会是这样:

csrftoken=cookie_from_test_huli_com; csrftoken=cookie_from_admin_huli_com
复制代码

但碰到同名 Cookie 的时候,不少人都会只取第一个处理,因此 Server side 收到的 csrftoken 就会是 cookie_from_test_huli_com

意思就是说,儘管你在 https://admin.huli.comSecure 的方式写了一个 Cookie,却被其余不安全的来源(http://test.huli.com)给复盖过去了!

那盖掉 Cookie 能够作什麽呢?举几个上面参考资料给的例子(但我不肯定有没有理解错误,有错的话请指正),第一个是 Gmail 的视窗不是分红两部分吗,一部分是信箱,另一部分是 Hangouts。攻击者能够利用上面讲的手法把原来使用者的 cookie 盖掉,换成本身的 session cookie,但是由于 Hangouts 跟 Gmail 自己的 domain 不同,因此 Gmail 仍是使用者的账号,Hangouts 却已经变成攻击者的账号了。

被攻击的人就颇有可能在不知情的情况下利用攻击者的账号来发送讯息,攻击者就能够看到那些讯息了。

第二个例子是某间银行网站,假如在使用者要新增信用卡的时候把 session cookie 换成攻击者的,那这张信用卡就新增到攻击者的账户去了!

大概就是这样,总之都是透过把本来的 cookie 遮蔽住,让 server side 使用新的 cookie 的攻击方法。

总结

我一开始碰到这个问题的时候真的满苦恼的,由于怎麽想都想不到为什麽一个语法彻底没错的指令没办法写入 Cookie,并且https://admin.huli.com这个网站我日常也不多用到,根本不会想到是它的问题。

但此次把问题解掉以后从新回来看,其实过程当中就有一些蛛丝马迹可循,例如说能够透过「清掉 Cookie 就没事」这点得知应该是跟其余 Cookie 有干扰,也能够从别的浏览器能够写入这点得知应该是 Chrome 的一些机制。

过程当中的每一个线索都会带你找到新的路,只要坚持走下去,必定能成功闯出迷宫。

相关文章
相关标签/搜索