CreepJS 不只讀取單一偽造值,而是交叉比對可信與不可信來源,揪出多數偽造工具藏不住的矛盾。
大多數指紋識別工具只回答一個問題:這個瀏覽器有多獨特? CreepJS 是一個開源指紋研究專案,問的卻是另一個問題:這個瀏覽器有沒有說謊? 它不只是收集一個 Canvas 雜湊或 WebGL 渲染器字串就了事,而是把每一項訊號拿來與同一個瀏覽器暴露的其他每一項訊號交叉比對——並與該訊號的真實實作理應呈現的行為相比對。當一個值單獨看起來合理,卻與瀏覽器透露的其他資訊相矛盾時,CreepJS 就把它稱為一個謊言,信任分數也隨之下降。本文將以概括的方式解釋這套測謊模型:「可信」與「不可信」來源代表什麼、竄改原型或 getter 為何會露出馬腳,以及為何同一類檢查既能識破反偵測瀏覽器,也能抓到粗糙的 user-agent 偽裝。
核心要點
- CreepJS 的核心理念是一致性,而非身份:它不需要一個「正確值」的資料庫,只需要證明瀏覽器自身的訊號彼此矛盾,或與真實實作理應呈現的行為相矛盾。
- 它會針對同一個事實比對可信來源與不可信來源——直接讀取一個屬性,對比間接推導出同一個值;或是把主執行緒的結果,拿來與在 Worker 中執行的同一運算比較——並標記出兩者之間的分歧。
- 原型與 getter 竄改,是偽造工具覆寫
navigator.userAgent或HTMLCanvasElement.prototype.toDataURL這類值的標準手法,會留下結構性證據:被改動的屬性描述符、不再符合原生程式碼的.toString()輸出,以及跨呼叫時會改變的參考相等性。 - 用來攔截屬性存取的 Proxy 陷阱本身也是可被偵測的,因為真正的瀏覽器 API 回應查詢時根本不需要
Proxy包裝器。 - 同樣的測謊邏輯,也正是為何粗糙的 user-agent 或 Canvas 偽造——甚至是精心打造的反偵測瀏覽器——往往會留下一整套矛盾模式,而不只是一個明顯的破綻。
CreepJS 究竟在測試什麼
根據其 GitHub 儲存庫的說明,CreepJS 的宗旨是「揭露現代反指紋擴充功能與瀏覽器中的弱點與隱私漏洞」。它執行一大套檢查——Canvas 與 WebGL 渲染、音訊與字型可用性、CSS 計算樣式、螢幕與時區資料、JavaScript 執行環境特徵等等——而不只是把結果雜湊成單一識別碼,它會評估每一項結果在內部是否合理可信。輸出結果是一個信任分數,加上逐項的細目分析:哪些訊號看起來是原生的,哪些看起來是拼湊、被修補過,或以其他方式與真實瀏覽器實例不一致。
這個框架,對任何想稽核自己設置的人來說都很重要。指紋唯一性工具告訴你,你有多容易被識別出來;測謊工具告訴你的,是你呈現出來的身分是否真的站得住腳——這更接近 Bot 防禦、詐欺評分與反偵測瀏覽器偵測系統在正式環境中實際檢查的內容,詳見網站如何偵測反偵測瀏覽器與指紋偽造。
測謊模型:一致性,而非身份
粗糙的指紋檢查只是讀取一個值並記錄下來。測謊式的檢查則會透過不只一條路徑去讀取同一個事實,再比對這些答案。如果一個瀏覽器真的如它所聲稱的那樣,那麼通往同一個事實的每一條路徑理應得出相同的答案,因為它們最終都源自同一個引擎、同一套渲染管線和同一個作業系統。如果有東西被修補成呈現一個虛假值,要讓每一條路徑都一致地被修補是非常困難的——而那些被漏掉的路徑,正是謊言現形之處。
這與一般的指紋一致性檢查背後的原理相同:唯一性衡量的是一個指紋有多罕見,而連貫性衡量的是它是否合理地可能屬於同一台真實裝置。CreepJS 把這個連貫性的概念套用在更細的顆粒度上——不只是「GPU 是否與所聲稱的作業系統相符」,而是「這個特定 API 的底層行為,是否符合該 API 真實實作應該產生的結果」,並在數十項個別檢查中重複驗證。
可信與不可信來源:謊言在哪裡現形
理解這類工具的一個實用方式,是把它們拆成針對同一個底層事實的可信來源與不可信來源。可信來源,是偽造腳本要攔截起來成本高或不容易做到的東西——例如從不同執行環境讀取的值、作為不相關程式碼副作用計算出來的值,或是從行為推導出來、而非腳本可以直接重新賦值的屬性。不可信來源,則是那些顯而易見、常被偽造的屬性本身——navigator.userAgent、Canvas 的 toDataURL() 呼叫、WebGL 參數——這些正是每個反偵測工具與隱私擴充功能早就知道要攔截的目標。
這項檢查問的不是孤立的「這個不可信的值是不是真的」;而是「這個不可信的值,是否與可信的值一致」。以下是這種模式常見的幾個例子:
- 主執行緒 vs. Worker 環境。 同一項運算——一次 Canvas 繪製、一次時區讀取、一個數學運算——可以同時在主執行緒和 Web Worker 內執行。真實的瀏覽器在兩處會產生相同的結果,因為這兩個執行環境共用同一個底層引擎。偽造工具經常只修補主執行緒的全域物件,卻漏掉了 Worker 獨立的全域範疇,導致兩邊結果不一致。跨情境 Canvas 一致性檢查——比較 Worker 中
OffscreenCanvas的輸出與文件附加 canvas 的輸出——正是這個通用模式的一個具體實例。 - 直接讀取 vs. 推導值。 有些事實既可以直接從一個屬性讀取,也可以間接推導出來——透過副作用、錯誤訊息,或只有真實實作才會正確產生的某種行為。如果直接讀取的值與推導出的值不一致,兩者之中只有一個可能是真的,而那個真的往往不是容易被修補的那一個。
- 聲稱身分 vs. 可觀察行為。 User-agent 字串聲稱了一種瀏覽器與引擎;而 JavaScript 引擎的計時、錯誤訊息措辭與功能存在與否,無論字串怎麼說,都會揭露真正在執行的是哪個引擎——這正是如何偵測 User-Agent 偽裝一文詳細解釋的機制。
這些檢查都不需要 CreepJS 知道你「正確」的指紋應該是什麼樣子。它只需要對同一個事實有兩扇獨立的觀察窗,並確信一個真實的瀏覽器不可能不從這兩扇窗透出相同的答案。
屬性描述符、Getter 與 Proxy 陷阱
除了跨來源比對之外,CreepJS 的方法論還包含直接的原型竄改偵測——尋找瀏覽器 API 已被覆寫的結構性證據,而不只是檢查其回傳值看起來是否合理。
偽造像 navigator.userAgent 或某個 Canvas 方法這樣的值,通常意味著重新定義一個 getter,或重新賦值一個原型方法。這種介入所改變的東西,遠不只是值本身:
- 屬性描述符。 對一個原生方法呼叫
Object.getOwnPropertyDescriptor(),會回傳特定、可預期的結構,以及"function name() { [native code] }"這樣的.toString()輸出。粗糙修補過的版本通常會回傳一個看得見的函式主體,或是一個忘了合成原生程式碼標記的覆寫版本。 - Proxy 陷阱。 用
Proxy攔截屬性存取,是動態修補 getter 的常見手法,但一個運作中的 Proxy 在參考相等性檢查下的表現,與原生屬性並不相同——存取它兩次可能會回傳可區分的包裝器實例,或者它的get/has陷阱會回應一些原生屬性根本不需要回答的探測。 - 參考相等性。 真正的原型方法,每次被存取時都是完全相同的函式物件。而以新建 closure 或動態生成的包裝器實作的修補版本,在重複存取之間可能無法通過
===比較——這是原生實作絕不會發生的情況。
這一切都無法證明真實的值應該是什麼——只能證明回應查詢的這個屬性,並不是瀏覽器出廠時內建的那一個。這往往是比猜測某個特定 Canvas 雜湊或 GPU 字串「看起來對不對」更強的信號,因為它完全不依賴任何已知正確值的資料庫。
為何粗糙的偽造與反偵測瀏覽器依然會失敗
貫穿所有這些檢查的共同主題是:偽造單一訊號很容易,但要一致地偽造一整套連貫的可信與不可信來源卻很難。一個覆寫了 navigator.userAgent 的腳本,很少會同時修補 Worker 環境中對應的等價值、調整每一個下游的屬性描述符,並為剛安裝的 getter 複製出原生程式碼的 .toString() 輸出。反偵測瀏覽器——專門打造、用來在整個設定檔中協調偽造值的 Chromium 分支——做得更接近完美,但即使是它們,也往往會留下同一類縫隙:一個只在主執行緒被修補、卻沒進入 Worker 的值,一個反應方式與原生屬性略有不同的 Proxy 陷阱,或是一個被可信來源檢查悄悄拆穿的聲稱身分。
這一點不會過時:隨著引擎與偽造技術不斷演進,指紋研究工具實際執行的具體檢查項目會持續擴充,但其底層模型——透過一條可信路徑與一條不可信路徑讀取同一個事實,並把分歧視為證據——不會過時。這也正是為何以連貫性為基礎的偵測,能夠持久地對抗一次性的花招:偵測器不需要窮舉所有可能的謊言,只需要不斷增加偽造工具必須同步、一致地修補的獨立路徑數量。
檢查你自己的瀏覽器
BrowserInsight 自家的工具也採用同樣「一致性優先」的思路,不需要你去解讀一份研究級的指紋儀表板。指紋檢測會呈現你的 Canvas、WebGL 與字型訊號,並附上會在它們對不上時發出標記的連貫性檢查;Bot 偵測工具則會執行一個防禦系統在正式環境中會用到的、以自動化與一致性為核心的檢查。如果你想要一份逐步進行的同類自查,瀏覽器指紋一致性自查清單會帶你完成七項具體的 UA/GPU/時區/字型檢查,幾分鐘內就能做完。這一切都不是教你如何通過 CreepJS 或任何其他測謊工具——理解這套模型的重點,在於看清楚真正被稽核的是連貫性,而不是任何單一的值。
常見問題
CreepJS 會蒐集或儲存我的指紋嗎?
CreepJS 是一個在你自己瀏覽器中執行的用戶端研究工具,讓你看到它揭露了關於你的哪些資訊;自己執行它的重點,是稽核你自己的訊號,而不是被它追蹤。在依賴任何託管式指紋識別服務之前,務必先查看該工具自身的隱私政策。
一個精心打造的反偵測瀏覽器,能騙過像 CreepJS 這樣的測謊工具嗎?
對一部分的檢查而言,有時候可以。要在一組不斷演變的檢查項目中,持續一致地協調每一個可信與不可信來源——主執行緒與 Worker 環境、每一個屬性描述符、每一個推導出的副作用——是很難長久維持的,這正是為何反偵測設置即使打造得再仔細,也往往至少會留下一道縫隙,詳見網站如何偵測反偵測瀏覽器與指紋偽造。
低信任分數,能證明一個真實使用者是機器人或詐騙者嗎?
不能。測謊分數衡量的是指紋的連貫性,而不是意圖。隱私擴充功能、較舊的瀏覽器版本,以及不尋常但合法的設置,都可能觸發個別檢查項目。正式環境的系統會把這項訊號與其他訊號結合起來判斷,而不會把它當成獨立的定論。
測謊與一般的指紋唯一性測試有何不同?
唯一性測試(EFF 的 Cover Your Tracks 是一個知名的例子)衡量的是,你的組合指紋在其他訪客之中有多容易被識別出來。測謊問的則是另一個問題:組成這個指紋的各項訊號,彼此之間、以及與真實瀏覽器行為之間,是否在內部保持一致——這是一個完全不需要拿你和任何人的指紋做比較的不同問題。


