TLS 會話票據能加速重連,但這張由伺服器簽發、你原樣回傳的票據,本身就是可關聯的 ID——不需要 Cookie、JavaScript,也不碰任何儲存 API。
大多數關於追蹤的講解只停留在瀏覽器這一層:Cookie、localStorage、用 JavaScript 算出來的指紋。但在這一切之下,HTTPS 還有一層,而且它自己保存狀態。TLS 交握完成後,伺服器可以交給瀏覽器一小塊不透明的資料——會話票據(session ticket)。瀏覽器把它存起來,下次連線時原樣回傳,藉此省掉大部分交握。它的本意是讓網頁更快;但它恰好也是一個識別碼:由伺服器簽發,由你原樣出示,而任何「清除 Cookie」按鈕都碰不到它。
核心要點
- 會話恢復是正當的效能功能。 在 TLS 1.3 中,伺服器在交握後傳送
NewSessionTicket訊息;用戶端在之後的連線裡憑它以預先共用金鑰恢復會話,不必重走完整交握。 - 票據天生就是一個 ID。 票據由伺服器產生,用戶端存著卻讀不懂,回傳時一字不改。只要伺服器在票據裡編碼、或在後台記下某個識別碼,就能認出回訪的用戶端——全程不涉及 JavaScript、Cookie 或儲存 API。
- 有效期就是追蹤視窗,而且可以接力續期。 RFC 8446 把單張票據宣告的有效期上限定為七天,但伺服器可以在每次恢復時再簽發一張新票據。只要用戶端在視窗內一直回訪,這條關聯就能一次次延長。
- 票據在網路上是明文可見的。 它放在 ClientHello 裡,因此鏈路上的被動觀察者能把重複使用同一張票據的連線關聯起來。RFC 8446 正是出於這個原因,要求用戶端不要在多個連線中重複使用同一張票據。
- 實際上它不是全域超級 Cookie。 現代瀏覽器會限定會話快取的作用範圍,讓第三方無法跨無關網站恢復會話,並在隱私瀏覽的邊界丟棄快取——但具體行為因瀏覽器和版本而異,請自行驗證,別想當然。
會話恢復是做什麼用的
完整的 TLS 交握是有成本的:要一次往返來協商參數,要傳送並驗證憑證,還要計算簽章。一個頁面開十幾條連線,這些開銷就累積起來了。會話恢復讓已經互相認證過的用戶端和伺服器跳過最耗時的那部分。
在 RFC 8446 定義的 TLS 1.3 中,這靠預先共用金鑰(PSK)實現。交握完成後,伺服器可以傳送一條或多條 NewSessionTicket 訊息(第 4.6.1 節),每條都帶有一張不透明的票據、一個有效期,以及用戶端推導對應 PSK 所需的材料。下次連線時,用戶端在 ClientHello 的 pre_shared_key 擴充中出示這張票據(第 4.2.11 節),再用 binder 證明自己持有對應的金鑰,伺服器就可以接受恢復,不必再走完整的憑證交握。
同一機制還支撐了 0-RTT 早期資料(第 2.3 節):用戶端在第一輪傳送(first flight)中就能帶上應用資料。RFC 坦言這有代價,尤其是早期資料可能被重送,並在第 8 節專門展開。但就本文而言,結論很簡單:會話恢復是個好功能,這裡沒有什麼後門。隱私問題出在誰持有狀態、狀態能存活多久,而不是密碼學本身有缺陷。
TLS 1.2 也有同樣的思路,分兩種形式——會話 ID 和會話票據(RFC 5077)——所以下文所說的並不是 TLS 1.3 才有的新問題。學術界多年前就指出過這種可關聯性,常被引用的是 Sy、Burkert、Federrath 和 Fischer 發表在 ACSAC 2018 的論文 Tracking Users across the Web via TLS Session Resumption。
票據如何變成識別碼
票據對用戶端是不透明的。瀏覽器看不到裡面是什麼,只是把伺服器給的東西存下來。正是這種不透明,讓它成為伺服器手中好用的積木,也讓它成了潛在的追蹤手段。
常見的設計有兩種:
- 自加密票據。 伺服器把會話狀態打包進票據,用只有自己掌握的金鑰加密,本機什麼也不存。任何持有該金鑰的伺服器都能解開它。
- 伺服器端查表。 票據只是一個隨機代號(handle),伺服器把狀態存在以這個代號為鍵的表裡。
無論哪種,票據裡裝什麼都由伺服器說了算,你回傳的每個位元組它也都看得到。協定並沒有禁止伺服器往裡面塞一個每位用戶端獨有的識別碼,或者乾脆記下回來的是哪個票據值。TLS 1.3 確實會對票據的年齡做混淆(obfuscated_ticket_age 欄位),但票據本身是原樣傳送的。結果就是一個穩定、可被伺服器識別的 ID,藏在 TLS 交握裡,而不在 HTTP 標頭或腳本可讀的儲存中。它和 ETag 快取超級 Cookie 是同一種套路,只是低了一層——而且和 ETag 不同,頁面裡的 JavaScript 讀不到、寫不了,也刪不掉它。
接力續期問題
七天上限聽起來像是天然的約束。RFC 8446 確實規定伺服器使用的票據有效期不得超過 604800 秒,也就是七天。但這個上限管的是單張票據,而不是票據背後的身分。
每次恢復成功後,伺服器都可以再發一條新的 NewSessionTicket。用戶端用新票據取代舊票據,下次造訪出示的就是新的那張。鏈條上的每一環都會把視窗往後推,所以只要用戶端每隔幾天至少重新連線一次,就可以被無限期地追下去,每張票據都把同一個底層身分往前傳。宣告的有效期能約束單張票據,卻約束不了一個不停重新簽發的伺服器。
這種接力正是多數講解略過的部分,也說明了為什麼「票據一週後就過期」會低估你每天都造訪的網站所帶來的暴露。不過在實務上,瀏覽器往往會對快取會話的重複使用時長設定更短的上限,而且記憶體中的快取在瀏覽器徹底結束時就會消失——所以真正的上限通常由用戶端決定,而不是 RFC 裡的七天。
為什麼實際上它不是超級 Cookie
如果據此認為「你造訪過的任何網站都能用 TLS 票據追蹤你」,那就錯了。風險的邊界取決於瀏覽器如何劃定會話快取的範圍:
- 誰能恢復哪個會話。 你在一個網站上建立的會話,不應被嵌入在另一個無關網站上的同一第三方伺服器恢復。依頂層網站對網路狀態(連線、HTTP 快取、TLS 會話)做分區是通行做法,這也和基於快取的追蹤廣為人知之後瀏覽器對 HTTP 快取的處理如出一轍。
- 隱私瀏覽與清除。 隱私視窗應當以空的會話快取開始、在關閉時丟棄;完整的「清除所有資料」或重新啟動瀏覽器,通常也會清空記憶體中的快取。
請把以上內容看作對一類防禦手段的描述,而不是對某個具體版本的承諾:各瀏覽器如何分區或清除會話快取一直在變,因此我們刻意不在這裡寫版本號。仍然存在的是同站追蹤:單一網站(或為它提供服務的 CDN)依然能認出你回訪的連線,網路觀察者也依然能關聯那些明顯重複使用了同一張票據的連線。
RFC 8446 中專門討論用戶端追蹤防護的附錄(附錄 C.4)講明了設計意圖:用戶端不應在多個連線中重複使用票據,因為重複使用會讓被動觀察者把這些連線關聯起來。遵循這條建議的瀏覽器能限制鏈路上觀察者取得的資訊,但對簽發票據的伺服器本身無能為力。
另一個影響:會話恢復會改變指紋
還有一個副作用,和本站對網路層的系列內容直接相關。恢復會話時的交握,在網路上看起來和完整交握不一樣。它會額外帶上 pre_shared_key 擴充(必須是 ClientHello 中的最後一個擴充),裡面裝著票據和 binder,還可能再加上 early_data 擴充。因此,擴充清單和 ClientHello 的總長度都會與首次連線不同。
JA3、JA4 這類 TLS 指紋是根據 ClientHello 計算的,所以同一個瀏覽器在恢復的連線上,可能算出與新建連線不同的值。如果你在用 TLS 指紋做偵測,或者正想弄明白為什麼同一個用戶端會出現兩個雜湊,會話恢復就是必須考慮的一個波動來源。具體原理見 TLS 指紋識別詳解和 JA4+ 套件;至於更新的傳輸協定,HTTP/3 與 QUIC 指紋識別裡也有同樣的現象。
它在各類持久識別碼中的位置
| 層 | 識別碼 | 由誰儲存 | 清除 Cookie 能清掉嗎? |
|---|---|---|---|
| 腳本 | 持久訪客 ID | 頁面 JavaScript,分散在多種儲存中 | 往往只能清掉一部分 |
| HTTP 快取 | ETag 超級 Cookie | 瀏覽器快取 | 不能 |
| TLS | 會話票據 | TLS 協定堆疊的會話快取 | 不一定 |
要說明的不是每一層都是災難——每一層都有瀏覽器的緩解措施——而是「我清過 Cookie 了」只涵蓋了這一整疊狀態存放點中最上面的一行。
你實際能做什麼
這些幾乎都無法在頁面內部觀察到,這本身就是問題所在。實際可行的做法是:
- 別指望清除 Cookie 就能重置 TLS 會話快取。 「清除 Cookie」是否會順帶丟棄快取的 TLS 會話,取決於瀏覽器;徹底結束並重新啟動瀏覽器,或者執行「清除所有資料」,是更可靠的辦法。
- 隱私視窗是最乾淨的邊界,因為它在設計上開始和結束時都不攜帶任何會話狀態。
- 別指望有工具能把你的票據顯示出來。 BrowserInsight 不會檢查你的 TLS 票據,也不回報會話恢復狀態。它能展示的是伺服器從你的 TLS 交握中觀察到的資訊——協定版本、加密套件、ClientHello 長度和擴充雜湊——就在指紋檢測的網路卡片裡,同樣的訊號也用於機器人偵測工具。需要注意,這些數值描述的是承載這次請求的那條連線,而它本身也可能是一條恢復的連線。


