HTTP ETag 標頭本是為快取重新驗證省頻寬而生——但伺服器只要給你一個獨一無二的 ETag,就能把瀏覽器快取變成追蹤 ID。
清除 Cookie 感覺像按下重置鍵,但其實只清空了一個特定的罐子。ETag 標頭的設計初衷相當平凡——告訴瀏覽器「你已經有這個檔案的最新版本了,不用重新下載」——而正是這個機制可以被挪為他用,悄悄給每位訪客發一個獨一無二、還會不斷被靜默確認的識別碼,它活在瀏覽器快取裡,而不是 Cookie 儲存空間裡。用這種方式打造的快取超級Cookie能撐過一次 Cookie 清除,因為它從頭到尾就不是 Cookie。
核心要點
- ETag 原本是用來標示快取檔案版本的,但沒有任何規則禁止伺服器把這個識別碼做成每位訪客獨一無二,而非每個內容版本獨一無二——這就把普通的快取重新驗證變成了一趟追蹤往返。
- 這項技術是真實存在過的,不是紙上談兵:Samy Kamkar 的 evercookie 專案早在 2010 年就實作出一套可用的 ETag 儲存機制;2011 年 KISSmetrics 被爆出利用 ETag(搭配 Flash Cookie)讓使用者特意刪除的 ID 復生,並因此引來集體訴訟。
- ETag 並非唯一的快取類追蹤載體——HSTS 旗標、favicon 快取等任何網站能設定、之後又能讀回的瀏覽器狀態,都曾被用來玩同樣的把戲;ETag 只是紀錄最完整的一個案例。
- 現代瀏覽器已經堵上這個漏洞的跨網站版本。 自 Chrome 86 起,Chrome 依頂層網站對 HTTP 快取做了分割,此後在某個網站設定的快取項目(及其 ETag)就無法再被另一個網站讀取——Safari 與 Firefox 也各自推出了自己的分割方案。
- 同站的 ETag 追蹤依然有效,因為分割機制只擋住跨站的快取共用——單一網站仍然可以給你的瀏覽器分配一個獨一無二的 ETag,並在你每次回訪這個網站時把它讀回來,全程不需要任何 Cookie。
ETag 到底是做什麼用的
ETag是伺服器附加在資源(圖片、指令碼、API 回應等)上的 HTTP 回應標頭,作為版本識別碼使用。MDN 對這個標頭的描述很直白:它「讓快取更有效率、更省頻寬,因為內容沒有變化時,網頁伺服器不需要重新傳送完整的回應」。這個值究竟怎麼產生並沒有統一規範;實務上通常是內容的雜湊值、最後修改時間戳的雜湊值,或是一個版本號。
重新驗證的流程很簡單:
- 你的瀏覽器請求一個資源,伺服器回傳內容以及一個
ETag: "abc123"標頭。 - 你的瀏覽器把內容和這個 ETag 值一起快取起來。
- 下次請求同一個資源時,瀏覽器不會盲目重新下載,而是送出
If-None-Match: "abc123"。 - 如果伺服器目前的 ETag 仍然相符,它就回覆一個不帶內容的
304 Not Modified——頻寬消耗降到最低;如果內容變了,就送出新版本以及新的 ETag。
這趟往返——瀏覽器證明自己已經有某樣東西,伺服器確認這樣東西是否仍是最新——形態上和 Cookie 驗證一模一樣。唯一的差別只是識別碼裝在哪個標頭裡。
ETag 如何變成追蹤識別碼
HTTP 規範裡沒有任何條款要求 ETag 必須代表內容版本。伺服器完全可以在你第一次載入某個追蹤像素或指令碼時,產生一個獨一無二、依訪客區分的 ETag,把這個值存在伺服器端並與你的工作階段綁定,再以 ETag: "visitor-8f2c91a4" 的形式送給你。你的瀏覽器沒辦法分辨這是一個合法的快取新鮮度權杖,還是一個追蹤權杖,於是老老實實地把它快取起來,並在此後每次請求這個資源時,透過 If-None-Match 把它原封不動地回傳——全程沒有任何 Cookie、沒有 localStorage 寫入,甚至完全不需要 JavaScript,就能在每次頁面載入時悄悄把你重新辨識出來。
這正是Samy Kamkar 的 evercookie 專案在 2010 年示範過的機制:除了 HTML5 儲存、Flash 本機共用物件等半打其他機制之外,它還包含一個由伺服器端支撐的 ETag 元件,把一組識別碼存進 HTTP ETag,再從下一次請求中把它讀回來——目的就是讓這個 ID 在 Cookie 被清除之後依然能夠復生。這個專案當初想證明的,正是一個鐵了心的追蹤者能拼湊出多少條獨立於 Cookie 之外的儲存路徑,而 ETag 快取最終證明是其中相當耐用的一條,因為刪除 Cookie 從來不會清空瀏覽器的 HTTP 快取。
這項技術很快就從概念驗證走向了實際部署:2011 年,研究人員與記者證實,分析服務商 KISSmetrics——服務對象包括 Hulu 與 Spotify 等網站——曾利用 ETag 搭配 Flash Cookie,讓使用者明確刪除過的追蹤識別碼復生(respawning)。由此引發的隱私爭議最終導致一起集體訴訟和解,這也是 ETag 追蹤被規模化部署、而非僅止於概念驗證的最清楚一個真實案例。
快取類追蹤不只 ETag 一種
ETag 之所以最受矚目,是因為它是最典型的例子,但它只是快取類超級Cookie這個更大家族中的一員——任何網站既能設定、之後又能讀回的瀏覽器狀態,只要脫離了 Cookie 罐子,都可能被拿來夾帶識別碼:
| 手法 | 設定了什麼 | 之後讀回什麼 |
|---|---|---|
| ETag 追蹤 | 快取資源上一個獨一無二的 ETag | 下次請求時的 If-None-Match |
| HSTS 超級Cookie | 一組子網域中,哪些被標記為 HSTS 強制 HTTPS(每一位元編碼 ID 的一部分) | 瀏覽器是否在不發起往返請求的情況下,把每個子網域升級為 HTTPS |
| Favicon 快取 | 一個帶獨一無二網址、與其他儲存分開快取的 favicon | 瀏覽器是重新請求它,還是直接使用快取副本 |
| Cache-Control 計時 | 某個資源是否存在於快取中 | 快取命中與重新擷取之間的載入耗時差異 |
這四種手法都和跳轉追蹤(bounce tracking)、CNAME 偽裝共用同一種結構性套路:它們利用的機制打從一開始就不是為追蹤而設計的,所以那些專門針對 Cookie 的防禦手段——攔截第三方 Cookie、清空 Cookie 罐子——對它們根本無效。
為什麼它能撐過 Cookie 清除與無痕模式
清除 Cookie 只會清空 Cookie 儲存空間。HTTP 快取是一個獨立的子系統,有自己的儲存空間,在大多數瀏覽器的隱私設定裡也有自己單獨的「清除」核取方塊——而使用者往往會跳過這個選項,因為「清除瀏覽資料」對話框會把快取和 Cookie 列成兩個互不相關的選項。寫進快取裡的 ETag 會一直待在那裡,不受影響,直到快取本身被清除或該項目自然過期。
私密/無痕視窗本身也幫不上什麼忙:一個私密工作階段在視窗開啟期間同樣會使用 HTTP 快取,所以同一個私密工作階段稍早設定的 ETag,和一般視窗裡的表現完全一樣——只有在視窗關閉時,它才會和其他一切一起被丟棄。我們關於網站如何偵測無痕瀏覽模式的文章討論的是一個相關但不同的問題——網站透過儲存配額的怪異表現察覺到你正處於私密視窗,這和快取類識別碼能否在一個工作階段內部持續存在,是兩個完全不同的問題。
瀏覽器的應對:快取分割堵上了跨站漏洞
最大的結構性修補其實並不是專門針對 ETag——它瞄準的是 HTTP 快取本身。從 Chrome 86 開始,Chrome 依頂層網站對 HTTP 快取做了分割:快取資源不再只用網址作為鍵值,而是用網址加上發起請求的頂層網站(在後續改進版本中還加上了頁框網站)一起作為鍵值。Chrome 團隊自己的說明文章明確點出這類濫用正是改動的動機之一——快取的存在與否曾被用來推斷瀏覽紀錄,也曾被用來植入跨站追蹤識別碼,而分割機制消除了讓這兩種手法都得以成立的共用快取。Safari 與 Firefox 此後也各自推出了類似的快取分割方案。
實際效果是:嵌在 site-a.com 上的追蹤指令碼,再也無法設定一個能在同一支指令碼載入到 site-b.com 時被讀回的 ETag,因為現在每個網站都擁有自己獨立隔離的快取分割區。跨站 ETag 追蹤——也就是曾讓某家廣告技術供應商能在自己嵌入的每個網站上關聯同一使用者的那個版本——在目前主流瀏覽器裡已經大致被堵死。
分割機制沒有解決的,是同站追蹤:你反覆造訪的單一網站,依然可以在你第一次載入時分配一個獨一無二的 ETag,並在你每次回訪時把它讀回來,全程都發生在該網站自己的分割區內,不涉及任何 Cookie。快取分割的設計目的是阻止跨站關聯,而不是阻止一個網站用非 Cookie 的方式辨識自己的回頭客。
如何偵測與緩解 ETag 追蹤
沒有一個像「攔截第三方 Cookie」那樣一鍵就能解決這個問題的開關,這也正是為什麼 Privacy Badger 自己那則關於偵測 ETag 超級Cookie 的 issue(EFForg/privacybadger#2136)至今仍未關閉:合法伺服器基於普通快取用途會持續設定 ETag,所以想從外部區分版本識別碼與追蹤識別碼,是一個真正棘手的啟發式問題,不是簡單的樣式比對就能解決。
有幾件事確實有幫助:
- 連 HTTP 快取一起清,別只清 Cookie。 大多數瀏覽器的「清除瀏覽資料」對話框會把「快取的圖片和檔案」列為獨立於「Cookie 和其他網站資料」的選項——兩個都要勾。
- 對不想被關聯的工作階段使用私密/無痕模式,因為它的快取(和 Cookie 一樣)會在視窗關閉時被丟棄——只是它沒辦法阻止追蹤發生在同一個工作階段內部。
- 在敏感情境下徹底停用快取,可以透過瀏覽器開發者工具的「停用快取」選項,或是一款能剝除/隨機化
ETag與If-None-Match處理邏輯的隱私擴充功能來實現——代價是失去 ETag 原本該帶來的頻寬節省。 - 放心把跨站情境交給快取分割處理——它目前已經是各大主流瀏覽器的預設行為,所以性價比最高的那項修補,已經在你什麼都不用做的情況下自動生效。
這些方法都不需要讀取頁面上的 JavaScript,因為這項技術的關鍵就在於它從不觸碰任何指令碼能檢查得到的儲存 API——它完全活在頁面內容載入之前交換的 HTTP 標頭裡。
ETag 的交換本身發生在頁面指令碼讀不到的那一層,但快取識別碼很少單獨出手——追蹤者會把它和你瀏覽器暴露的其他一切拼在一起。BrowserInsight 的指紋檢測會展示追蹤者可能與快取類 ID 組合使用的 canvas、WebGL、儲存等用戶端訊號,全部在你的瀏覽器裡測量,絕不外傳做任何分析。
常見問題
無痕/私密視窗能阻止 ETag 追蹤嗎?
只能在工作階段之間發揮作用,工作階段內部不行。私密視窗的快取會在你關閉它時被丟棄,所以那次工作階段中設定的 ETag,沒辦法在你重新開啟瀏覽器後被讀回——但只要這個私密視窗還開著,ETag 式的重新辨識就和一般視窗裡表現得一模一樣。
攔截第三方 Cookie 能阻止 ETag 超級Cookie 嗎?
不能,而這正是這項技術當年引起關注的全部原因。第三方 Cookie 攔截專門檢查的是 Cookie 標頭;它對 ETag 或 If-None-Match 毫無感知,所以一個基於快取的識別碼可以毫無阻礙地穿過 Cookie 攔截器。真正能阻止跨站版本的,是 HTTP 快取分割——一個完全獨立的瀏覽器機制。
VPN 或單純清除 Cookie 能阻止這一切嗎?
兩者都不行。VPN 改變的是你的 IP 位址,不是瀏覽器的快取內容。清除 Cookie 清空的是 Cookie 儲存空間,卻完全不動 HTTP 快取——以及躺在裡面的任何 ETag,而這恰恰就是這項技術專門用來利用的那道縫隙。
ETag 追蹤現在還在用嗎?
自從 Chrome、Safari 與 Firefox 都推出了 HTTP 快取分割之後,跨站 ETag 追蹤的效果已經大打折扣——分割機制阻止了在一個網站設定的快取項目(及其 ETag)被另一個網站讀取。同站 ETag 追蹤——單一網站重新辨識自己的回頭客——在技術上依然可行,而且完全不在快取分割的管轄範圍內。
結語
ETag 追蹤提醒我們,「清除你的 Cookie」這句建議從頭到尾都只針對某一種特定的儲存機制,而不是「不會再被辨識」的保證。HTTP 快取的存在是為了讓網路更快,而任何一種伺服器既能寫入、又能讀回的機制——一個 ETag、一個 HSTS 旗標、一個 favicon 的網址——一旦被賦予獨一無二性而非真正的內容版本意義,就隨時可能被改造成一個識別碼。快取分割堵上了這個問題裡危害最大的跨站版本;同站的這一半問題規模較小、動靜也較小,但下次你清了 Cookie 卻發現好像什麼都沒被重置的時候,它依然值得你多留意一下。
推薦閱讀:


