Cookies 的跨域指令碼攻擊 - Github 遷移域名的安全詳解

發表於2013-04-15

英文原文 :Yummy cookies across domains,編譯:oschina

上週五我們宣佈並完成了把GitHub Page服務遷移到新域名github.io。 這是個計劃已久的行動,此舉是為了防止惡意網站攻擊和跨域cookie的漏洞,這些漏洞是通過在我們主站的子域名下控制客戶內容產生的。

關於這些跨域攻擊漏洞的可怕影響,大家可能會有一些困惑。我們希望這篇技術部落格可以消除這些困惑。

一個二級域名傳過來的Cookie

當你登陸GitHub.com時,我們通過響應的HTTP頭部來設定session的cookie。這個cookie包含著唯一標識你的session資料:

這些GitHub傳送給瀏覽器的session的cookie是設定在預設的域名上(github.com),這就意味著這些cookie是不能從二級域名*.github.com訪問到的。而且我們也指定了HttpOnly屬性,這意味著cookie也不能通過JavaScript 的API:document.cookie來讀取。最後,我們指定了Secure屬性,這意味著這些cookie只能通過HTTPS來傳輸。

因此,從GitHub託管網站讀取或”竊取”session的cookie是不太可能的。通過在GitHub網站託管的使用者程式碼是不容易獲取到session的cookie,但由於瀏覽器通過HTTP請求來傳送cookie,這種方式有可能把cookie從GitHub網站拋到GitHub父域名上。

當瀏覽器執行一個HTTP請求時,它通過header裡單獨的cookie傳送一些和URL匹配的cookie,這些傳送的cookie是以鍵-值對存在的。只有和請求的URL匹配的cookie才會傳送出去,比如,當執行一個對github.com的請求時,設定在域名github.io上的cookie是不會傳送的,但在github.com上的cookie將會傳送。

Cookie丟擲的問題是因為header中的cookie只包含了一系列鍵值對的cookie,並沒有一些其他資訊, 通過這些額外資訊可以知道cookie設定在哪個域名上,比如路徑或者域名。

最直接的跨域攻擊涉及到:在GitHub託管網站頁面,通過document.cookie這個JavaScript API設定一個_session的cookie。假設這個網站託管在*.github.com,那麼這個cookie將會被設定到父域名的所有請求裡,儘管事實是它只設定在了二級域名裡。

在這個示例中,通過JavaScript在二級域名上設定的cookie被髮送旁邊合法的cookie欄位中,並設定到父域名裡。如果域名,路徑,Secure和HttpOnly屬性未設定的話,根本 沒有方法去判斷哪個cookie來自哪裡。

這對大部分web伺服器來說是一個大問題,因為在一個域及其子域中的cookies的順序並不是有RFC6265指定的,並且web瀏覽器可以選擇以任何順序傳送它們。

對於Rack–為Rails和Sinatra提供動力的web伺服器介面,包括其他的,cookis解析如下:

如果在Cookie:header裡有不止一個有著相同名字的cookie時,第一個cookie將會被假定成任意值。

這是一個很顯而易見的攻擊:幾周之前,安全專家Egor Homakov在部落格中就用這個方法證明了該攻擊確實存在.這個漏洞的影響是不嚴重的(每次登入後,跨站點偽造請求的令牌會被重置,所以它們不會一直固定不變),但這是個非常實際的例子,人們可以很容易偽造登出使用者,令人很鬱悶.這使得我們必須儘快完成把GitHub頁面遷移的他們自己的域名,但只留給我們幾周的時間(到遷移完成之前),在這期間我們必須減輕已知的攻擊數量.

幸運的是,已知的攻擊在服務端很容易減輕.我們預想到會有一些其他的攻擊,這些攻擊或者很難處理,或者根本不可能存在.那麼讓我們一起看看這些它們.

免受cookie丟擲的傷害

第一步是減輕cookie丟擲造成的攻擊.這個攻擊暴露出瀏覽器將會傳送2個相同名字的cookie令牌,不讓我們知道它們是設定在哪個域名上的.

我們沒法判斷每個cookie是來自哪裡的,但如果我們跳過cookie的解析,我們就能看出每個請求是否包含2個相同的_session的cookie.這個極有可能是由於有些人從二級嘗試域名丟擲這些cookie,所以我們不是猜測哪個cookie是合法的,哪個是被拋過來的,而是簡單地通知瀏覽器在繼續執行之前放棄二級域名上設定的cookie.

為了完成這個示例,我們創造了一個特殊的響應:我們讓瀏覽器跳轉到剛剛請求的URL,但帶著一個Set-Cookie的header,這個header放棄了二級域名上的cookie.

我們決定按照Rack中介軟體來實現這個功能.這種方式在 應用執行之前可以執行檢查cookie和順序重定向的工作.

當觸發Rack的中介軟體時,在使用者不會意識到的情況下,這個重定向會自動發生,並且第二個請求將只會包含一個_session的cookie:合法的那一個.

這個”破解”足夠減緩大部分人所遇到的直接丟擲cookie的攻擊,但還有一些更復雜的攻擊也需要我們思考一下.

Cookie路徑方案

如果一個惡意的cookie設定到一個具體的路徑,這個路徑不是根路徑(例如,/notifications),當使用者訪問github.com/notifications時,瀏覽器會傳送那個cookie,當我們在根路徑上清除這個cookie時,我們的header不會起作用.

 

這個方案非常直截了當,雖然不太雅:對於任何指定的請求URL,如果其路徑部分匹配請求的URL,瀏覽器將只會傳送一個惡意的JavaScript cookie.所以我們只需要在每個路徑的元素上放棄這個cookie就可以了.

 

當談到cookie時,我們需要在服務端做關聯.我們唯一目的是用這個強力方式清楚那些cookie,這種方式雖然暴力,但完成github.io的遷移後,效果非常好.

Cookie溢位

讓我們加強我們的遊戲:另一種攻擊將會執行,它利用RFC 6265沒有明確指明一種cookie的溢位行為.大部分web伺服器/介面,包括Rack,假定cookie的名字可以加密(如果他們包含不是ASCII的字元時,這就是個瘋狂的假設),所以當生成cookie列表時不會溢位:

這就允許一個惡意使用者去設定一個cookie,這個cookie能被web框架理解成_session,儘管在瀏覽器裡這個cookie的名字並不是_session.這個攻擊會把沒必要溢位的cookie字元溢位掉:

如果我們試著丟棄Rack產生的cookie列表中的第二個,我們的header就會失效.在Rack解析以後,我們會失去重要的資訊:通過加密後的cookie名字和web框架接收到的名字將不一致.

為了解決這個問題,我們必須跳過Rack的cookie解析,可以通過禁用不溢位,然後找到所有和我們目標匹配的那些cookie名字.

這種方式我們可以丟棄對的cookie(它或者設定成_session,或者作為溢位的偏差).在這種中介軟體的幫助下,我們可以解決所有的能在服務端解決的cookie丟擲引起的攻擊.不幸的是,我們意識到有另一種攻擊會使得中介軟體的保護失效.

Cookie溢位

如果你遇到了cookie的問題,我為你惋惜. 我已經有了99個cookie,我的域名不能再增加一個了.

這是一個稍微更高階的攻擊,它暴露出所有web瀏覽器對每個域名設定的cookie的數量限制.

比如,火狐設定的數量限制是150,谷歌瀏覽器設定的是180.問題是這個限制不是在每個cookie的域名屬性上設定的,而是通過cookie設在的實際域名來定義的.一個單獨的HTTP請求訪問主域名和子域名上的任一頁面,將會傳送最大數量的cookie,但哪些cookie被使用的規則確實沒有定義的.

例如谷歌瀏覽器不會關心父域名上的那些cookie,這些cookie是通過HTTP設定或者用Secure設定的:它將會傳送180個新的cookie.這使得非常容易”剔除”每一個單獨的從父域名過來的cookie,並用一些子域名上用JavaScript執行的假的cookie來替代它們:

在子域名上設定了180個這樣的cookie之後,所有從父域名過來的cookie就消失了.如果現在我們終止我們剛剛設定的cookie,也包含JavaScript那部分,那麼子域名和父域名上的cookie列表就會變空:

 

這允許我們執行一個只帶有一個_session的cookie的單獨的請求:這個cookie是我們用JavaScript創造的.原來的Secure和HttpOnly在_session的cookie裡沒有了,並且沒有方法在web服務端檢測傳送的cookie既不是Secure,HttpOnly,也不是在父域名中設定的,但是完全虛構的.

在服務端只設定一個_session的cookie,就沒有方法知道cookie是否被丟擲了.即使我們發現了一個不合法的cookie,這樣的攻擊也僅能把使用者從GitHub登出.

結論

正如我們看到的,通過在瀏覽器溢位cookie,我們可以製造帶有惡意cookie的請求,這些請求不能在服務端阻隔.這裡沒有什麼新的知識:Egnor的原始概念攻擊的證據和暴露在這裡的變種攻擊都早已被世人所知.

現在看起來,在子域名上控制使用者自定義的內容是一種安全的自殺行為,尤其在谷歌瀏覽器當前實現方案下,更加劇了這種自殺行為.火狐處理父域和子域上cookie區別的方式更優雅(用更一致的排序來傳送它們,並且分離它們的儲存來防止子域的溢位),谷歌瀏覽器就沒有這種區別,並且對通過JavaScript設定的cookie和伺服器通過Secure,HttpOnly設定的cookie一視同仁,導致一個非常完美的丟擲攻擊.

不管如何,通過HTTP header來傳輸cookie的行為是模糊的和依賴於實現的,遲早會有人提出另一種跨域丟擲cookie的方式,和目標瀏覽器無關.

當cookie丟擲的攻擊並不是太危險的(比如,攔截使用者session,或者實行網路欺詐/騷擾使用者是不太可能的),它們會直截了當的進行,這非常使人惱火.

我們希望這篇文章能幫助大家提高這些攻擊問題的防範意識和不通過遷移域名來防止這些攻擊的困難點,所以遷移域名是一個激進的但最終必須的措施.

相關文章