更改 user-agent 幾乎騙不了人。了解網站如何透過標頭順序、client hints、JS 引擎探測與指紋矛盾識破偽裝。
User-agent 偽裝,是指更改瀏覽器送出的 user-agent 字串,好讓網站誤以為你正在使用另一種瀏覽器、裝置或作業系統。這件事很容易做到——一個設定或一個擴充功能就能搞定——但面對一個真正在留意的網站時,它很少奏效,因為 user-agent 只是你的瀏覽器釋出的數十個訊號之一,而其餘訊號會悄悄拆穿這個謊言。本文將解釋偽裝是如何被偵測的:標頭一致性、client hints、JavaScript 引擎探測,以及指紋矛盾。
核心要點
- user-agent 字串只是一個自我回報、可任意編輯的聲稱——單憑它伺服器什麼也查證不了,因此現代偵測會把它當作一個需要交叉比對的、未經查證的斷言。
- 瀏覽器還會洩漏數十種其他訊號(HTTP 標頭順序、
sec-*標頭、User-Agent Client Hints、JavaScript 引擎行為、canvas/WebGL/螢幕/觸控),它們全都必須與所聲稱的身分相符。 - 偽裝之所以失敗,是因為偽造一行文字輕而易舉,而始終一致地偽造一個瀏覽器整套可被觀測的行為卻極其困難。
- 偵測是機率性的:每一項矛盾都會抬高懷疑分數,而不是給出一個非黑即白的判定。
- 引擎層級的行為與 HTTP 標頭一致性是最難偽造的幾項訊號,因為它們需要複製一個真實瀏覽器的完整實作。
什麼是 user-agent 偽裝?
你的瀏覽器發出的每一個請求都包含一段 user-agent 字串——一行類似 Mozilla/5.0 ... Chrome/120.0 ... 的文字,宣告著瀏覽器、版本與平台。偽裝指的就是刻意更改這段字串。人們這麼做的理由很多:隱私(融入人群或破壞追蹤)、存取限定某些瀏覽器才能看的內容、網頁擷取、自動化測試,或者——在惡意的那一端——把詐欺與機器人流量偽裝成真實使用者。
更改字串本身微不足道。問題在於,user-agent 只是一個聲稱,而瀏覽器還會洩漏關於自身的數十項事實,伺服器可以拿這些事實與該聲稱交叉比對。一旦它們對不上,偽裝就露餡了。
為什麼不能只信任 user-agent 字串
一個只讀取 user-agent 的伺服器,等於全盤接受瀏覽器對自己身分的說法。但 user-agent 是自我回報且可任意編輯的,因此單憑它什麼也證明不了。現代偵測會把它當作一個未經查證的斷言,然後追問:這個瀏覽器所做的其他一切,真的符合它所聲稱的那種瀏覽器的真實樣貌嗎?通常並不符合,因為偽造字串很容易,偽造一個瀏覽器整套可被觀測的行為卻極其困難。
網站如何偵測被偽裝的 user-agent
偵測的運作方式,是蒐集各自獨立的訊號,再檢查它們彼此之間是否內部一致。主要技術如下。
HTTP 標頭一致性(順序與 sec-* 標頭)
不同的瀏覽器會送出不同的 HTTP 標頭,順序不同,值也不同。Chrome 會送出一組 Firefox 沒有的 sec-ch-ua 與 sec-fetch-* 標頭;User-Agent 標頭在請求中的位置會因瀏覽器而異;而 Accept、Accept-Language、Accept-Encoding 的值也遵循各瀏覽器特有的模式。如果一個請求聲稱是 Chrome,卻缺少 Chrome 的 sec-* 標頭——或者像 Firefox 那樣排列標頭——這個聲稱就很可疑。這些都不會顯現在 user-agent 字串本身之中,而大多數偽裝工具也懶得去複製它們。
Client Hints 與 user-agent 的矛盾
Chromium 系瀏覽器會暴露 User-Agent Client Hints,這是一個結構化、可由 JavaScript 讀取的來源,透過 navigator.userAgentData API 提供相同的瀏覽器/平台資訊。伺服器可以索取高熵的 client hints,再拿它們與 user-agent 字串比對。如果 user-agent 說是「macOS 上的 Safari」,但 client-hints API 卻存在,並回報自己是 Windows 上的 Chromium,那麼兩者就互相打架,偽裝一目了然——因為真正的 Safari 根本不會暴露 Chromium 的 client hints。
JavaScript 引擎與功能探測
每種核心對 JavaScript 與網路平台的實作都略有不同,而這些差異在程式碼中是可被觀測的。偵測器可以檢查所聲稱瀏覽器理應存在的 API 是否存在、測試核心特有的怪癖,或執行微型基準測試,以其時序為 JavaScript 引擎留下指紋。一個暴露了 Chrome 專屬 API 的「Firefox」,或者其引擎行為符合 V8 而非 SpiderMonkey 的瀏覽器,就是被偽裝的。這直接關聯到瀏覽器真正使用的算繪引擎——核心遠比名稱難偽造。
指紋矛盾(canvas、WebGL、螢幕、觸控)
最後,裝置本身也會洩漏一些理應與所聲稱的瀏覽器與平台相符的訊號:
- Canvas 與 WebGL 的算繪會因 GPU、驅動程式與作業系統而異;一個行動端的 user-agent 背後卻是桌面端 GPU 的簽章,就是一面紅旗。關於這是如何運作的,請參閱 Canvas 指紋檢測。
- 螢幕尺寸若大於可見視窗,或與所聲稱的裝置類別不符,便顯得可疑。
- 觸控支援:一個手機的 user-agent,背後的裝置卻回報沒有任何觸控事件,這很少說得通。
每個訊號單獨來看都很微弱,但合在一起,便構成一份 user-agent 必須去符合的畫像——而一次簡單的字串更改是跟不上的。
伺服端與用戶端訊號如何拆穿被偽裝的 UA
偽裝之所以站不住腳,是因為它必須同時騙過兩個截然不同的觀察者:讀取原始 HTTP 的伺服器,以及在頁面內執行的 JavaScript。一個修補了其中一方的偽裝工具,往往忘了另一方,於是兩邊的說法便不再一致。
在伺服端,在任何指令稿執行之前,請求本身就已經帶著破綻。HTTP/2 標頭的組合與順序、是否存在 Chromium 專屬的 sec-ch-ua 標頭,以及 TLS ClientHello,都在頁面被解析之前就已被觀測到。尤其是 TLS 交握會產生一個穩定的簽章——通常概括為 JA3 或 JA4 雜湊——它源自用戶端所提供的密碼套件與擴充。這個簽章反映的是底層網路堆疊(Chromium 的 BoringSSL、Firefox 的 NSS),而非 user-agent 字串,所以一個聲稱是 Firefox、卻呈現 Chromium TLS 指紋的請求,本身就自相矛盾。這一切都無法透過在瀏覽器設定裡更改 user-agent 來改變。
在用戶端,JavaScript 可以直接盤問執行階段。navigator.userAgentData 要麼存在(Chromium),要麼不存在(Firefox、Safari)——一段被偽裝的字串無法憑空把這個 API 變出來。除此之外,引擎專屬全域物件與 API 的有無、數字與日期的精確序列化方式、錯誤訊息的措辭,以及緊湊迴圈的時序特徵,全都會暴露真正在執行的是哪種 JavaScript 引擎。就連 CSS 也會出賣引擎:帶廠商前綴的屬性、表單控制項的預設算繪,以及透過 CSS.supports() 查詢的支援旗標,在 Blink、Gecko 與 WebKit 之間各不相同。偵測器要做的,不過是看這些用戶端事實是否與標頭和 user-agent 的聲稱對得上。
聲稱 vs 實際:幾個有代表性的例子
| 所聲稱的身分 | 真實實例本應呈現 | 粗糙偽裝常常呈現 |
|---|---|---|
| macOS 上的 Safari | 沒有 sec-ch-ua 標頭;navigator.userAgentData 未定義;WebKit 的 CSS 破綻 | Chromium 的 sec-ch-ua 標頭與已定義的 userAgentData |
| Windows 上的 Firefox | Gecko 的標頭順序;SpiderMonkey 的時序;沒有 Chromium client hints | V8 風格的引擎行為與 Chromium 的 TLS/JA3 簽章 |
| iPhone Safari(行動端) | 存在觸控事件;WebGL 回報行動端 GPU;螢幕較小 | 桌面端 GPU 算繪器字串且不支援觸控 |
| Android 上的 Chrome | sec-ch-ua-mobile: ?1;ARM GPU 字串 | sec-ch-ua-mobile: ?0 或桌面端 NVIDIA/Intel GPU 字串 |
每一列都是偵測器可以標記的矛盾,而它無需「證明」user-agent 為假——只要注意到兩個相互獨立的訊號不可能同時為真即可。
如何自己動手測試
你不需要一個伺服器叢集就能看到這一點。打開瀏覽器的 DevTools 主控台,並排執行 navigator.userAgent 與 navigator.userAgentData:在 Chromium 上後者回傳一個物件,在 Firefox 與 Safari 上則是 undefined,無論你偽裝成什麼字串都一樣。用網路面板查看瀏覽器實際送出的請求標頭,確認 sec-ch-ua 是否存在。接著用擴充功能或 DevTools 的裝置模擬更改你的 user-agent 再重複一遍——你會看到字串變了,而 client hints、引擎行為與 GPU 字串還和原來一模一樣。這道落差,正是偵測系統所衡量的東西。
檢查你自己的瀏覽器是否存在矛盾
你可以在自己身上看到許多這類訊號。執行 BrowserInsight 的瀏覽器核心檢測,把你所聲稱的瀏覽器與它真實的算繪引擎做比對;再用機器人檢測工具,看看一個自動化系統會權衡哪些一致性訊號。如果你使用了會更改 user-agent 的隱私工具,這也是你判斷它們是否引入了新的矛盾、使你變得更容易被辨識而非更難的方法。
常見問題
更改我的 user-agent 違法嗎?
不違法。更改 user-agent 是瀏覽器與擴充功能允許你做的、正常且合法的事,它也有測試與相容性等正當用途。法律問題只會來自你用它做了什麼——詐欺或未授權存取——而不是來自更改字串這個行為本身。
網站能 100% 確定地偵測出偽裝嗎?
不一定能百分之百確定,但往往能有很高的把握。偵測是機率性的:每一項矛盾(缺少標頭、client hints 不符、引擎行為錯誤、指紋衝突)都會抬高一個懷疑分數。一次讓所有訊號都保持一致的精心偽裝確實很難抓,但一般的 user-agent 更改會留下許多破綻。
隱私工具與反指紋擴充功能會觸發誤判嗎?
有時會。會更改 user-agent 或隨機化訊號的工具,可能製造出被偵測系統判讀為偽裝的矛盾,這也是注重隱私的使用者偶爾會面臨額外機器人挑戰的原因之一。設計良好的工具會一致地更改訊號以避免此問題;粗糙的工具則會讓你更顯眼。
偵測偽裝最可靠的訊號是什麼?
並沒有單一的訊號——可靠性來自把它們組合起來。話雖如此,引擎層級的行為與 HTTP 標頭一致性是最難偽造的幾項之一,因為它們需要複製一個真實瀏覽器的完整實作,而不只是編輯一行文字。
結語
User-agent 偽裝容易嘗試卻難以得手,因為瀏覽器透露的關於自身的資訊,遠多於它主動奉上的那串字串。標頭順序與 sec-* 標頭、client hints、JavaScript 引擎行為,以及裝置指紋,全都必須與那個聲稱相吻合——而一行字的編輯無法讓它們保持一致。無論你是在防護一個網站,還是在稽核自己的隱私設定,得到的教訓都一樣:永遠不要只信任 user-agent,並檢查它周圍的那些訊號。
推薦閱讀: