被凍結的 User-Agent 版本號可能已經不真實。這篇文章說明網站如何用特徵偵測反推你真實的瀏覽器引擎版本,以及這個偵測方法本身的極限。
你的 User-Agent 字串寫著 Chrome/143.0.0.0。這是真的嗎?有兩件事讓你沒辦法單憑這串文字判斷。第一,User-Agent 縮減把主版本號之後的所有數字都凍結成寫死的 .0.0.0,唯一會隨每次更新變動的那部分版本資訊就此消失。第二——也是更關鍵的一點——整串 UA 本來就是瀏覽器對自己的聲明,一個擴充功能或一套自動化框架用一行程式碼就能改寫它。一個真正需要知道你瀏覽器引擎版本的網站,必須問一個完全不同的問題:不是「瀏覽器自稱是什麼」,而是「它實際上具備哪些能力」。這就是基於特徵偵測的版本推斷,也正是 BrowserInsight 自家瀏覽器核心檢測背後所用的技術。
重點摘要
- 特徵偵測版本推斷的做法是:測試某個在某個引擎版本中已經上線的能力是否存在,同時測試下一個版本才會上線的能力是否缺席,藉此把真實引擎鎖定在一個狹窄的區間內。
- 結果永遠是一個區間,而不是精確的版本號——功能是分批上線的,有些還帶實驗開關或被向後移植(backport)到舊版本,所以這項技術能鎖定一個版本區間,卻鎖不到
chrome://version裡那串四段式的完整版本號。 - 每一套這樣的偵測工具都有一個內建的天花板:它只能測試建表時就已經存在的功能,一旦你的瀏覽器通過了表裡的每一項測試,報告就只能說「至少是這個版本」。
- 兩個讀數的不一致是有方向的:特徵偵測出的版本低於 UA 聲稱的版本,通常只代表偵測表已經過時;而特徵偵測出的版本高於 UA 聲稱的版本,才是真正指向偽造的方向——瀏覽器可以隱瞞版本,卻沒辦法演出它並不具備的能力。
- 同樣的探測方式在版本號出現之前,就已經能把 Blink、Gecko、WebKit 區分開——因為這三種引擎的功能組合本身就是各自獨立演化的。
偵測機制:用特徵探測鎖定版本區間
瀏覽器引擎的每一次發布,都會帶來一批可被觀察的新 Web 平台能力——一個 CSS 屬性、一個 JavaScript 方法、一個 DOM 介面。這些能力中的大多數都能被頁面直接測試,不需要跳出權限提示,也不需要使用者互動:檢查某個屬性是否存在、某個建構函式是否已定義、某個 CSS 值是否被接受。
一個最簡單的存在性探測大致是這樣:
// `Intl.Locale.prototype.variants` 在 Chromium 149 上線。
const has149 = 'variants' in Intl.Locale.prototype;
// CSS 的 `text-fit` 屬性在 Chromium 150 上線。
const has150 = CSS.supports('text-fit', 'initial');
// has149 && !has150 -> 這個引擎就是 Chromium 149。
這兩行程式碼都不會下載任何東西,也不會碰網路——它們只是向引擎詢問一個關於自身能力的是/否問題。關鍵在於成對地選擇探測項:一個是已知在某個版本上線的功能,另一個是已知在這個版本還不存在、但下一個版本會上線的功能。如果第一項通過、第二項沒通過,瀏覽器就被鎖定在「是這個版本,但還不到下一個版本」的區間裡。把足夠多這樣的探測對,依照已知的發布版本表跑一遍,就能沿著階梯往上走,找到瀏覽器完全滿足的最高版本——這就是真實引擎版本的下限估計,完全不依賴 UA 字串聲稱了什麼。
這正是 BrowserInsight 自家瀏覽器核心檢測背後的做法:它會走一張 Chromium 版本表(從 Chromium 79 起)和另一張獨立的 Firefox 版本表,逐一測試每個版本已知的功能差異,並根據「通過」鏈條在哪裡中斷來回報推斷出的版本——同時把 User-Agent 聲稱的版本也一併展示,方便你直接看出兩者是否一致。上面那兩項探測就是直接取自這張表。
為什麼答案是一個區間,而不是一個精確數字
特徵偵測矩陣永遠無法像 chrome://version 那樣解析出精確的組建編號——它只能鎖定一個里程碑版本,即便如此也有幾點必須先說清楚:
- 功能是成批上線的,而不是一個版本一個,所以單獨一項通過的測試很少能單獨鎖定某一個版本——往往需要好幾項確認性與排除性測試搭配,才能收窄區間。
- 部分功能帶實驗開關,在正式對所有人開放前,會在一個或多個版本裡藏在實驗旗標後面,所以處於某個版本、卻還沒被灰度到該功能的一小部分使用者,可能會在「本該通過」的測試上失敗。
- 向後移植確實會發生:某個安全相關的功能有時會在正常發布節奏之外,被塞進某個更舊的穩定版通道,這會讓某個舊版本在這一項測試上看起來比實際更新。
這些都不會讓這項技術失效——只是意味著誠實的輸出應該是「這個瀏覽器至少是版本 N」,而不是「這個瀏覽器精確等於版本 N.N.N.N」。任何工具,包括本站的瀏覽器核心檢測在內,如果把特徵偵測包裝成能解析出精確修補版本號,那都是在誇大這套方法實際能做到的事。
為什麼每一套偵測工具都有天花板
一張特徵偵測表本質上是一份快照:它只能測試建表當時就已經存在、且已知的能力。一旦某個瀏覽器通過了表裡的每一項測試——因為它確實比這張表涵蓋的最新版本還要新——就沒有任何測試項能再攔住它,誠實的報告只能是「這個版本或更新」,而不是一個精確數字。這不是某個具體實作特有的錯誤,而是這套方法本身結構性的極限,這一點同樣適用於 BrowserInsight 自己的瀏覽器核心檢測。偵測表需要隨著引擎不斷發布新版本、以及新的可區分特徵不斷出現而定期更新——Chrome 自己的功能路線圖和它的已上線功能清單是官方記錄這些資訊最主要的公開來源,這正是維護這類偵測表的人必須持續追蹤的內容。caniuse.com 則是核對單一功能在各引擎間支援時間線的一個實用交叉參照,這也是為什麼維護者通常不會只依賴某一家瀏覽器廠商自己發布的更新日誌。
不一致該往哪個方向讀
有了兩個讀數,下一步自然是拿來對比。但兩種不一致的價值並不對等,把它們當成對稱的,正是一個版本偵測工具開始把一般使用者誤判成偽造者的原因。
- 特徵偵測出的版本低於 UA 聲稱的版本,通常根本不是謊言。這只是上一節說的天花板在現實中的樣子:瀏覽器比偵測表新,通過了表裡的每一項測試,於是被回報在表裡最高的一檔上。BrowserInsight 的瀏覽器核心檢測刻意不為這種情況給出偽造判定——只要偵測表沒有在新版本發布的同一週就跟上,這條判定就會對每一個把瀏覽器升到最新的訪客誤報。
- 特徵偵測出的版本高於 UA 聲稱的版本,才是真正可疑的方向。瀏覽器可以選擇不聲明自己的版本,卻沒辦法演出它並不具備的能力。只存在於更高版本的功能,配上一個聲稱更低版本的 UA,代表那串字串被改寫過,而底下的引擎仍在說實話。
還有第三種模式完全不牽涉 UA:看通過/不通過的結果是不是一道乾淨的階梯。真實的引擎會通過直到自己版本為止的每一檔,並在更高的檔位上全部失敗,中間不留空缺。低檔沒通過、高檔反而通過了,這根本不是任何一個版本,而是真實組建不會呈現的形狀——它指向的是一個被打過修補或被模擬出來的環境,而不是一個過時的瀏覽器。由於這項判據只把各個探測結果互相比對,即使 UA 字串缺失、籠統或根本是亂碼,它依然成立。
跨引擎:同一種技術,在版本之前先分辨引擎
特徵偵測的作用不只是鎖定版本號——它同樣是頁面在版本號出現之前,先把不同引擎區分開的方式。Blink、Gecko、WebKit 各自何時、以何種順序上線實驗性與新標準化的功能,一直存在分歧,這種分歧早在它們各自到達目前版本之前就已經開始。一個存在於 Blink 但不存在於 Gecko 的功能,無論目前跑的是哪一個具體的 Chromium 或 Firefox 版本,都足以把兩者區分開來。想了解這三種引擎為何會以這種方式各自分化的更深背景,可以看瀏覽器核心解析:Blink、Gecko 與 WebKit;想弄清這項技術所探測的算繪引擎,與隨之打包的獨立 JavaScript 引擎之間的差異,可以看算繪引擎 vs JavaScript 引擎。
同樣值得說清楚的是,這項技術偵測到的到底是什麼。Chrome、Edge、Brave、Opera 以及其他所有基於 Chromium 的瀏覽器,共用同一個底層 Blink 引擎,所以特徵偵測探測回報的是這個共用引擎的版本——「Chromium 143」——而不是包裹在外層的品牌產品。Chromium 與 Chrome 詳細解釋了這種區分為何存在、為何不是錯誤,Chromium 壟斷則解釋了為什麼這麼多互不相關的瀏覽器廠商最終都選擇共用同一個核心。
為什麼這項技術變得更有用,而不是更沒用
特徵偵測不是一個曾經管用、後來被更好方案取代的權宜之計——隨著其他版本信號的變化,它反而變得更有價值,而不是更沒有價值:
- **User-Agent 字串曾經會隨真實更新而改變。**在 User-Agent 縮減之前,瀏覽器的 UA 會回報一個接近真實修補版本的數字,所以那時用特徵反推版本更多只是個技術趣聞。現在,UA 裡的版本號被設計成固定停在
.0.0.0,兩次修補更新之間根本不會變化——它本來就該看起來是靜態的,而且對每一個保持最新的安裝來說都確實如此。特徵偵測是剩下的、唯一沒有被歸零的版本信號。 - User-Agent Client Hints,具體來說是
Sec-CH-UA-Full-Version-List請求標頭以及與之搭配的navigator.userAgentDataAPI,重新帶回了精確的版本號——但這只是瀏覽器對同一個自我聲明事實的第三種表述方式,被放在一個需要明確請求才會觸發的機制後面,而不是預設就廣播出去。這確實是一條有用、資訊更詳細的通道,但它同樣可以被偽造 UA 的同一套工具偽造,因為它和 UA 一樣,都來自瀏覽器的自我回報。 - **多個讀數之間的不一致,現在是一個真正的信號。**當你有了三個相對獨立的來源——凍結的 UA、Client Hints 聲稱的版本、以及特徵偵測出的引擎版本——一個在兩條自我聲明管道上都宣稱同一件事、行為表現卻完全是另一個引擎版本的瀏覽器,正是如何偵測 User-Agent 偽裝一文所討論的那種矛盾。而特徵偵測是這三者中唯一一個瀏覽器無法單方面自行宣稱的讀數。
在自己的瀏覽器上驗證一下
BrowserInsight 的瀏覽器核心檢測會立即在你的瀏覽器上跑完這套探測:它把特徵偵測出的引擎版本與 User-Agent 聲稱的版本並排展示,給出兩者是否一致的判定,並逐個版本列出完整的通過/不通過結果,讓你看清自己的階梯停在哪一檔。
如果你想先親眼看看這項技術的效果,可以打開開發者工具執行 CSS.supports('text-fit', 'initial')。回傳 true 代表你跑的是 Chromium 150 或更新的版本;回傳 false 則代表你要嘛低於這個版本,要嘛根本不是這個引擎——不論哪一種,這都是直接從引擎上讀到的一級階梯,完全不經過 User-Agent。
常見問題
特徵偵測能查出我 Chrome 的精確組建編號嗎?
不能。它能把你的引擎鎖定到某個里程碑版本——例如「至少是 Chromium 149,但還沒到 150」——但 chrome://version 裡那些尾端的組建編號與修補號,它始終搆不著。功能是按版本發布的,不是按修補發布的,所以修補等級的精確度從原理上就不是這項技術能提供的。
Firefox 和 Safari 也適用這種方法嗎?
區間鎖定的邏輯本身與引擎無關,但功能對照表卻是針對每種引擎分別建置與維護的。BrowserInsight 的瀏覽器核心檢測跑的是一張 Chromium(Blink)版本表和一張 Firefox(Gecko)版本表;WebKit 的版本編號方式雜訊更大,本站目前還沒有對應的偵測矩陣,不過同樣的引擎識別步驟仍然適用於它。
如果一個瀏覽器在表裡的每一項測試都沒通過,是不是代表它是個很舊的瀏覽器?
不一定——也可能是這個瀏覽器比表裡已知的最新版本還要新,所以表裡沒有任何一項能把它和這個上限區分開來。全部通過和全部不通過,在一張過時的表面前看起來是一樣的;只有具體的通過/不通過模式(而不是一面倒地全過或全不過),才能告訴你自己究竟站在這張表天花板的哪一側。
為什麼不直接信任 User-Agent Client Hints 給出的版本號?
Client Hints 確實給出了一個精確的版本號,但這依然是瀏覽器透過另一個管道對自身的一種陳述——而不是被獨立驗證過的行為表現。相較之下,特徵偵測更難被令人信服地偽造,因為要偽造它,瀏覽器就得真正實作(或足夠逼真地模擬)表裡直到所聲稱版本為止的每一項功能,而不只是回傳一串不同的字串。
結語
特徵偵測探測回答了一個被凍結的 User-Agent 字串已經無法誠實回答的問題:這個瀏覽器根據它實際能做到的事、而不是它自稱的身分,究竟執行在哪個引擎版本上。答案會是一個有天花板的區間,而不是一個精確的組建編號——這是這種方法本身誠實的極限,而不是某個具體實作獨有的缺陷。它的價值不在於精確,而在於獨立性。在瀏覽器關於自身的所有陳述裡,這是唯一一個瀏覽器無法單方面自行宣稱的信號,也正因如此,它才值得拿來和瀏覽器說的其他一切互相印證。
推薦閱讀:


