轉自:高級前端進階html
幾個禮拜前我在工做上碰到了一些跟Cookie 有關的問題,在這以前,我本來想說:Cookie 不就那樣嘛,就算有些屬性不太熟悉,上網找一下資料就行了,哪有什麼跟Cookie 有關的難題?django
然而事實證實我錯了。我還真的碰到了一個讓我解超久的Cookie 問題。瀏覽器
相信看到這邊,不少人應該躍躍欲試了,那我就先來考一下你們:安全
什麼情形下,Cookie 會寫不進去?
bash
像是語法錯誤那種顯而易見的就不用說了,除此以外你可能會答說:寫徹底不一樣domain的Cookie。例如說你的網頁在http://a.com卻硬要寫http://b.com的Cookie,這種情形固然寫不進去。cookie
或者,你可能會回答:不在https卻想加上Secureflag的Cookie。 沒錯,像是這種情形也會寫不進去。session
除了這些,你還能想到什麼嗎?dom
若是想不太到,那就聽我娓娓道來吧!ide
在一個月前我寫了一篇跟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 的一些機制。
過程當中的每一個線索都會帶你找到新的路,只要堅持走下去,必定能成功闖出迷宮。