我遇過的最難的Cookie問題

鳴珂發表於2019-04-02

轉自:高階前端進階

mp.weixin.qq.com/s/UxySd528X…

前言

幾個禮拜前我在工作上碰到了一些跟Cookie 有關的問題,在這之前,我原本想說:Cookie 不就那樣嘛,就算有些屬性不太熟悉,上網找一下資料就好了,哪有什麼跟Cookie 有關的難題?

然而事實證明我錯了。我還真的碰到了一個讓我解超久的Cookie 問題。

相信看到這邊,很多人應該躍躍欲試了,那我就先來考一下大家:

什麼情形下,Cookie 會寫不進去?

像是語法錯誤那種顯而易見的就不用說了,除此之外你可能會答說:寫完全不同domain的Cookie。例如說你的網頁在http://a.com卻硬要寫http://b.com的Cookie,這種情形當然寫不進去。

或者,你可能會回答:不在https卻想加上Secureflag的Cookie。 沒錯,像是這種情形也會寫不進去。

除了這些,你還能想到什麼嗎?

如果想不太到,那就聽我娓娓道來吧!

悲劇的開始

在一個月前我寫了一篇跟CSRF有關的文章(讓我們來談談CSRF),正是因為工作上需要實作CSRF的防禦,所以趁機研究了一下。簡單來說,就是要在Cookie設定一個csrftoken。

可是那天我卻發現,我怎麼寫都寫不進去。

我的測試網站的網址是: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 set、unable 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。

撥雲見日

難得讓我找到了一點線索,當然要跟著這條線索繼續查下去。

回想了一下,發現是另外一個負責後臺管理的網站叫做: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。
複製程式碼

在大概確認問題以後,我就開始調整各個變因,看能不能查出到底是哪一個環節出了問題,最後我發現兩個重點:

其實只有Chrome 不能寫,Safari, Firefox 都可以

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.com的csrftoken這個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.com用Secure的方式寫了一個Cookie,卻被其他不安全的來源(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 的一些機制。

過程中的每個線索都會帶你找到新的路,只要堅持走下去,一定能成功闖出迷宮。

相關文章