問題
Cookie允許無狀態的HTTP協議支援有狀態會話,在web上,我們依靠Cookie實現了很多有趣的功能。即便如此,Cookie依然還是有很多問題:使用起來不夠安全,浪費資源,使用一種令人驚訝的方式追蹤使用者在網路上的活動。
安全:這些年我們引入過很多的特性,試圖提供合理的安全屬性給那些關心安全的開發者,但也只是降低了安全問題而已:
-
Cookies對JavaScript來說預設是可用的(透過document.cookie),這使得一次XSS平滑升級能偷竊持久憑證(然後能使用Cookies在記憶體中產生類似於Spectre的有效攻擊)。雖然十年前引入了HttpOnly屬性,目前也只有大概8.31%的人使用Set-Cookie進行了設定。
-
預設情況下,Cookie會被髮送到非安全的源,這會導致憑據被盜。Secure屬性將為安全的源Cookie鎖定,這很好!儘管如此,今天只有大概7.85%的人Set-Cookie進行了設定。
-
Cookies經常在請求傳送者沒有任何跡象的情況下被設定。SameSite屬性用於減少CSRF風險,但是事實上,目前只有大概0.06%的人使用Set-Cookie進行了設定。
Cookies使用屬性來降低安全風險,然而它依然不符合我們對其他web資源設定的安全邊界。它們透過給定的註冊域名跨域訪問,它們忽略埠和scheme(意味著它們很容易被網路攻擊者偽造),它們能縮小到詳細的路徑。這些特徵使得它們很難落地,很難為平臺的其他地方降低同源策略起到激勵作用。
效率低:服務能透過一個給定的註冊域名儲存很多Cookie,並且很多Cookie可以透過HTTP請求被髮送。不同的瀏覽器供應商有不同的限制,但是它們都很高。例如Chrome,允許為每一個域名儲存180個Cookie,相當於硬碟上的724kB。幸運的是,如果請求頭中發送了很多的資料,伺服器通常會出問題。Cookie濫用的情況事實上非常多:一般(未壓縮)的Cookie請求頭大小是409位元組,但是90%的請求是1589位元組,95%的請求是2549位元組,99%的請求是4601位元組(~0.1%的Cookie頭非常大,超過10kB)。
隱私:Cookies一方面能開啟監控(我希望我們可以在某種程度上解決這個問題,透過類似HTTP上Cookie的弊端描述的機制那樣放棄它),另一方面,使用者可能不瞭解全面跟蹤的事情。
註意:上面提到的所有統計資料來自於Chrome的2018年7月的資料。我非常歡迎其他瀏覽器供應商提供類似的資料,但是我假定它們在同一個數量級。
提議
讓我們透過為開發人員提供一條良好的路徑來解決上述問題,可以起到防範安全的作用。使用者代理能管理HTTP狀態,相當於在使用者這邊透過為使用者瀏覽的每個安全的源生成一個唯一的256位大小的Token。這個Token能作為結構化的HTTP請求頭,交付給源:
Sec-HTTP-State: token=*J6BRKagRIECKdpbDLxtlNzmjKo8MXTjyMomIwMFMonM*
這個標識或多或少像代理控制的Cookie,包含一些重要的差別:
-
代理管理Token值,而非服務端管理。
-
Token將僅僅在網路層可用,JavaScript不可用。
-
使用者代理將只為每個源產生一個256位大小的Token,並且將不會暴露任何它產生的Token給源。
-
Token將不會被產生,或者傳遞給不安全的源。
-
預設情況下,Token將會在相同網站的請求中傳遞。
-
Token持續存在,直到它被服務或者使用者或者代理重置。
這些差別可能不能改寫所有的使用者場景,但是一般情況下足夠了。對於不夠用的情況,我們支援開發者透過HTTP頭的Sec-HTTP-State-Options選項來進行控制。選項如下:
-
一些服務將必須跨站訪問它們的Token。另外一些服務可能希望將範圍縮小到同源請求。兩種方案都支援:
Sec-HTTP-State-Options: ..., delivery=cross-site, ...
或者
Sec-HTTP-State-Options: ..., delivery=same-origin, ...
-
一些服務將希望限制Token的生命週期。我們允許它們去設定TTL(單位秒):
Sec-HTTP-State-Options: ..., ttl=3600, ...
時間過期之後,這些Token的值將自動被重置。服務可能也希望去明確指定Token的重置(例如登出)。設定TTL為0能達到目的:
Sec-HTTP-State-Options: ..., ttl=0, ...
在任何情況下,都可以向當前執行的頁面通知使用者的狀態變化,以便執行重置操作。當重置發生,使用者代理能傳送一個叫http-state-reset的訊息到源的BroadcastChannel(並且可能喚醒伺服器響應使用者重置):
let resetChannel = new BroadcastChannel('http-state-reset'));
resetChannel.onmessage = e => { /* Do exciting cleanup here. */ };
-
對一些服務來說,代理產生Token將足夠狀態維護。他們能像一個會話標識一樣處理它,並且系結使用者狀態到服務端。另一些服務需要額外工作來保證信任Token的來源。為此,服務能產生一個唯一id,服務端使用它跟會話id關聯起來,並且透過HTTP響應頭傳遞它到代理:
Sec-HTTP-State-Options: ..., key=*ZH0GxtBMWA...nJudhZ8dtz*, ...
代理將儲存這個id,並且使用它去給資料集生成簽名,減少Token抓取的風險:
Sec-HTTP-State: token=*J6BRKa...MonM*, sig=*(HMAC-SHA265(key, token+metadata))*
舉例,使用已經定義的Signed Exchanges格式來簽名請求,除了重放攻擊之外,其他情況都很難使用被盜的令牌。在請求中包含時間戳可以減少重放攻擊的可能性。
註意:這種特定情況沒有被解決。我們需要去重新審視人們在系結Token等方面所做的工作,以確定正確的威脅模型是怎麼樣的。把它當成是一個方向去探索,這還不是一個深思熟慮的穩妥解決方案。
FAQ
繼續上面提到的三個方面,這個針對建立一個固化的狀態Token的建議,對映到與平臺其餘部分的相同安全原語,減少客戶端的傳輸成本,並且預設不支援跨站追蹤。
我們應該放棄Cookie?
當然不是!
那為什麼提出本方案?
Cookie是不好的,並且我們應該發現一種方式去放棄它。但是這需要花費一段時間。該提案旨在提供一種補充,即使在Cookie同時存在的情況下,也是有價值的方案,給我們一種推動開發者從一個方案增加另一個方案的能力。
與Cookie對比,它完全是一個新的東西?
不完全是。
開發人員可以透過設定像“__Host-token=value1; Secure; HttpOnly; SameSite=Lax; path=/”這樣的Cookie來獲得幾乎所有上面提到的屬性。這不是一個完美的方案,但它非常好。我瞭解到的現狀是,開發人員需要瞭解各種標誌和命名約定,才能透過Set-Cookie:token = value這樣的方式使用它。預設值很重要,這似乎是最簡單的事情。比起讓開發人員使用4個屬性,以及採用奇怪的命名約定來說,將它合併到1個屬性裡更好。
我們還可以重新考慮伺服器端狀態在代理上維護。我覺得使用使用者代理控制會話識別符號,而不是伺服器設定的大量鍵值對,更優雅,更節約使用者的資源。
你期待大家怎麼從Cookie遷到這個方案上?
使用者代理可以透過在傳出的請求頭附加Sec-HTTP-State的方式,逐步遷移,來廣播對新功能的支援(預設情況下設定這個值,或允許開發人員根據上面的討論選擇性支援)。
開發人員可以開始將新機制用於其身份驗證基礎架構,這些機制最大程度地受益於源的作用域,與現有Cookie基礎架構並行。隨著時間的推移,他們可以建立一個他們所依賴的客戶端狀態串列,並開始在會話識別符號和狀態之間構建伺服器端對映。一旦該機製成熟了,以逐步遷移的方式遷移。
最終,您可以想象讓開發人員能夠完全遷移,完全關閉其網站的Cookie(例如,透過Origin Manifests)。最終,我們還可以讓開發人員選擇使用Cookie而不是完全放棄。
在整個時間線的任何時刻,使用者代理都可以像HTTP上Cookie的弊端裡提到的那樣,透過對Cookie子集進行限制來開始遷移。
對於多應用的源,這種遷移是不是很困難?
是的。這似乎是一個bug,又是一個功能。對於源和應用來說,1:1關係會更好。在同源的應用程式之間沒有安全邊界,我假設存在一個其他應用似乎沒有多大價值。鼓勵不同的應用在不同的源上執行,在它們的功能之間建立實際的隔離。
該提案是否對隱私屬性有重大改變?
不完全是。絕大多數沒有。也就是說,人們仍然可以使用這些Token來跟蹤源的使用者,就像他們今天使用Cookie一樣。人們需要透過設定Token的delivery成員來宣告這個意圖,有一點小的要求,需要使用者代理以某些方式對該宣告作出合理的響應,但就其本身而言,技術能力幾乎沒有變化。
儘管如此,它仍然比現狀有一些優勢。例如:
-
這些Token不會以明文形式傳送,這降低了監控的風險。
-
使用者代理控制Token的值,這樣可以降低在使用者本地磁碟上暴露使用者敏感資訊的風險,降低暴露您和使用者之間TLS終端的風險。
-
預設情況下,delivery選項會將Token限製為同源請求。假設我們遵循Cookie的SameSite 約定,使用者跨站訪問,需要明確說明Token可以跨站(此時使用者代理才會做出相應的處理)。
使用者可以如何管理使用者代理?
就像今天使用Cookie一樣,使用者可以選擇不將此Token傳送到任何地方。使用者代理會朝著那個標的努力,但使用者也可以隨時選擇不適用使用者代理。
相關閱讀:
本文作者 Mike Wes,在Google的Chrome團隊就職,鄧啟明翻譯。轉載本文請註明出處,歡迎更多小夥伴加入翻譯及投稿文章的行列,詳情請戳公眾號選單「聯絡我們」。
高可用架構
改變網際網路的構建方式
長按二維碼 關註「高可用架構」公眾號