Intl API 暴露了瀏覽器內建的 ICU 語系資料庫。本文說明哪些 Intl 呼叫會洩漏指紋、為何能推斷瀏覽器版本,以及為什麼難以一致偽造。
問大多數人 JavaScript 的 Intl API 裡哪一項被用來做指紋識別,答案幾乎都是時區。時區確實最出名,我們在時區與語系洩漏一文中已有詳細說明。但 Intl 並不是單一訊號,它是一扇通往瀏覽器內建語系資料庫的窗口,而這個資料庫體量大、帶版本號,並且隨引擎、平台與建置版本而不同。你能向它提出的問題,攜帶的資訊量遠比一個時區字串多。
核心要點
Intl暴露的是編譯進瀏覽器或作業系統的 ICU 類語系資料,因此它的回答取決於引擎、作業系統與建置版本,而不只是你設定的語言。- 三個語系值可能互相矛盾:
navigator.languages(對外宣告)、Accept-Language請求頭(實際送出)、Intl.DateTimeFormat().resolvedOptions().locale(執行階段協商的結果)。這種矛盾本身就是訊號。 Intl.supportedValuesOf()會回傳曆法、排序規則、貨幣、數字系統、時區與單位的長清單。整份清單就是指紋,同時還能標出你的建置年代。Intl.NumberFormat、Intl.DateTimeFormat與Intl.Collator的格式化輸出會因資料版本而異;在遵循作業系統地區設定的瀏覽器上,即使介面語言是英文,預設值也可能暴露你所在的地區。- 資料存在於二進位檔內部,很難一致地偽造。Tor Browser 與 Firefox 的
privacy.resistFingerprinting可以把它統一為en-US,代價是使用體驗。
時區:一段話帶過
沒錯,Intl.DateTimeFormat().resolvedOptions().timeZone 是最知名的 Intl 洩漏點,它與 IP 所在國家不符是經典的 VPN 特徵。這裡不再重複,這個主題由時區與語系洩漏負責。下面談的是 Intl 攻擊面的其餘部分,它們遠沒有那麼多人討論。
三個語系值,三個來源
瀏覽器內部對「你的語言」有好幾種說法,它們來自不同的地方:
navigator.languages是瀏覽器向指令碼宣告的有序語言清單。Accept-Language是瀏覽器在每個請求中實際送出的內容。resolvedOptions().locale(見Intl.DateTimeFormat及其同類物件)是執行階段把你的請求與它真正擁有的語系資料比對之後協商出的結果。
在未經改動的瀏覽器裡,這三者以可預期的方式對應。如果某個工具改了前兩項卻忘了第三項,頁面看到的就是一個執行階段自己都不認可的「宣稱身分」。這與指紋一致性和 User-Agent 偽造偵測是同一套邏輯:單一數值不足以定罪,矛盾本身才是證據。
熵最高的一項:Intl.supportedValuesOf()
Intl.supportedValuesOf(key) 接受 calendar、collation、currency、numberingSystem、timeZone 或 unit 之一,回傳執行階段支援的該類數值的排序清單。這些清單很長,並且與建置內建的語系資料版本同步變化。
關鍵在於:指紋是整份清單,而不是其中任何一項。新增一種貨幣、一個被改名或新收錄的時區、多出一個單位,都會改變清單。由於語系資料版本隨瀏覽器與作業系統發布而更新,這份清單還相當於建置日期戳記:不讀取 User-Agent 字串,也能把你定位到某一段發布區間。把六份清單做個雜湊,就得到一個精簡的識別碼,只要瀏覽器資料不變它就穩定,並且與使用相同資料的所有人共用。
把格式化輸出當作探針
另一種做法是不再列舉,而是直接格式化:給執行階段一個固定輸入,比較它回傳的結果。
Intl.NumberFormat決定分組分隔符號、小數點、貨幣符號位置與精簡記數法。Intl.DateTimeFormat.formatToParts()把日期拆成帶標籤的部分,各部分的順序以及它們之間的字面字串都能被看到。Intl.Collator決定帶重音、大小寫混合與中日韓字串的排序方式。
不同的資料版本與不同的平台資料,對同一輸入可能給出可觀察到的不同輸出。我們刻意不列出特定版本的具體輸出字串,因為它們會變動,且我們沒有逐一驗證;重點是差異的類別。追蹤者不需要知道正確答案是什麼,只需要對許多答案取一個穩定的雜湊。
與作業系統地區而非語言綁定的預設值
視瀏覽器與平台而定,有些 Intl 預設值會跟隨作業系統的地區設定,而不是你在瀏覽器裡選的語言。Apple 裝置上的 Safari 最為典型;Chrome 與 Firefox 則大多依據瀏覽器本身的語言設定決定預設值,這本身就是引擎之間的一項差異。可能受影響的值包括:
- 預設曆法(西曆、佛曆、日本年號曆等);
- 預設數字系統(拉丁數字、阿拉伯-印度數字、天城文數字);
- 時制(12 小時或 24 小時)與每週第一天。
一個人可以在介面語言為英文的瀏覽器裡閱讀英文頁面,而作業系統卻設成了泰國或沙烏地阿拉伯地區。在遵循該設定的瀏覽器上,navigator.language 顯示的是英文,resolvedOptions() 卻悄悄透露了地區。這與語言請求頭是不同的訊號,可用來推測裝置是在哪裡完成初始設定的。
為什麼難以一致地偽造
反偵測瀏覽器可以改寫 User-Agent、navigator.languages 與時區字串,因為這些只是 JavaScript 物件上的值。而語系資料本身是固化在二進位檔或作業系統裡的。要保持自洽,偽造層必須攔截每一個 Intl 建構函式、每一個 supportedValuesOf 鍵、每一次 formatToParts 呼叫,還有數字、日期與字串上的 toLocaleString,並給出與宣稱的平台和版本一致的答案。漏掉一處,宣稱的身分就與執行階段自相矛盾。想了解這類缺口在實務上如何被發現,可參閱反偵測瀏覽器與真實瀏覽器的比較。
我們介紹這些訊號,是為了讓你能理解並檢測它們,而不是提供偽造它們的方法。
緩解措施及其代價
Tor Browser,以及啟用了 privacy.resistFingerprinting 的 Firefox,透過標準化來因應這一攻擊面:時區一律回報為 UTC;如果你接受「要求英文版網頁」的提示,語系也會對所有網站統一回報為 en-US。人群裡每個人看起來都一樣,這正是目的所在。
代價是真實存在的:日期、數字與排序會依一個並非你本人的語系來格式化,而信任該回報值的網站會給德國或日本的使用者顯示錯誤的習慣格式。標準化是用便利換匿名,這也是大多數瀏覽器預設不這麼做的原因。
這個訊號有多大?
客觀衡量,Intl 屬於中等熵的貢獻者,達不到 Canvas 那個量級。目前版本 Chrome 上的 en-US 這類常見語系,其 Intl 輪廓與極多人相同。對冷門語系、作業系統與瀏覽器地區混搭,以及舊版或自訂建置,這個訊號的區辨度會更高;它最大的價值在於與其他訊號交叉驗證。如何看待這一點,請參閱熵與匿名集。你可以用指紋檢測查看自己的瀏覽器回報了什麼,它完全在瀏覽器本機執行,不會向伺服器傳送任何資料。

