網頁無法直接詢問你是否裝了廣告攔截器,只能靠一個誘餌廣告元素、一次被攔截的網路請求,或缺失的指令碼全域變數來推斷出結果。
網頁沒有辦法直接問出「你是否裝了廣告攔截器?」這樣的問題——它沒有對應的瀏覽器 API,不能像查詢你的時區或螢幕尺寸那樣查詢它。於是,想知道答案的網站不會去問,而是設下一個小陷阱,看它會發生什麼事。陷阱活下來,代表什麼都沒被攔截;陷阱沒活下來,就代表有什麼東西起了作用。
核心要點
- **網頁是從結果反推廣告攔截器的存在,而不是靠某個直接的訊號。**三種常見方式是:一個用來測試過濾規則是否命中的誘餌元素、一個發往廣告類位址卻失敗的網路請求,以及一個廣告指令碼本該定義、卻始終沒能定義的全域變數。
- **無論攔截器是傳統擴充還是宣告式規則引擎,同樣的偵測方式都能生效。**靠指令碼運作的傳統擴充,和 iOS 上的 WebKit 內容攔截器、Chrome 的
declarativeNetRequest這類宣告式引擎,執行的是同一批過濾規則,因此在頁面上留下的可觀察痕跡是相同的。 - **一次偵測本身只回傳「有東西被干擾了」這一個位元,但哪些誘餌元素被命中,能把範圍進一步收窄。**不同過濾清單針對的類別名稱和網址模式各不相同,哪些誘餌存活、哪些沒能存活,本身就是一個小小的指紋訊號。
- **基於請求的偵測,在完全沒裝廣告攔截器時也可能誤判。**DNS 層級過濾、瀏覽器自帶的追蹤保護或企業代理伺服器,都能造成一模一樣的「廣告沒載入」——儘管它們都無法隱藏誘餌元素。
- **這篇文章講的是偵測原理,不是規避方法。**文中不包含任何隱藏攔截器、或破解反廣告攔截彈窗的方法——那屬於另一個對抗性的話題,本文不涉及。
每一次廣告攔截器偵測背後的三種方式
拋開各種變體,幾乎所有網頁上的廣告攔截器偵測,都可以歸結為頁面載入後不久執行的三種測試之一。
**誘餌元素。**網頁建立一個 <div>(或類似元素),它的類別名稱和結構與真實廣告位置一模一樣——比如 ad-banner、pub_300x250、text-ad 這樣的名字,尺寸和位置也和真實廣告容器一樣。網頁把它插入頁面,稍等片刻。廣告攔截器主要依靠過濾清單(EasyList 及其同類清單)按選擇器比對元素,因此真正的攔截器會把這個誘餌連同任何符合同一模式的真實廣告一起隱藏或刪除。隨後網頁檢查誘餌是否還在:display 是不是被設成了 none、offsetHeight 是不是變成了零、節點是不是被徹底從 DOM 中移除。只要中招一項,就代表有東西攔截了它。BrowserInsight 自己的外掛與擴充偵測用的正是這種測試:插入一個位於螢幕外、帶有 pub_300x250、text-ad、ad-banner 等類別名稱的 div,約 100 毫秒後讀取它的計算樣式與版面尺寸。
注意,這是一種純粹的外觀層測試。誘餌是網頁自己的指令碼在本機建立的,全程不涉及網路請求——只有能在頁面內部執行元素隱藏規則的東西,才能讓它消失。
**被攔截的網路請求。**網頁不去觀察某個元素,而是發出一個看起來像廣告或追蹤呼叫的請求——路徑裡帶著 /ads/、主機名稱符合某個已知的廣告放送網域、指令碼名稱看起來像某個已知的分析標籤。帶有網路過濾規則(不只是外觀隱藏)的攔截器,會在請求離開瀏覽器之前把它攔下,或者瀏覽器會把這次回應回報為失敗。網頁自己的指令碼能看到這個失敗——一次被拒絕的 fetch、一張觸發了 onerror 而不是 onload 的圖片——並把它當作訊號。
**缺失的全域變數。**真正的廣告放送指令碼一旦成功載入,通常會在全域作用域裡定義點什麼——一個函式、一個設定物件、一個發布方自己的程式碼在繼續之前會檢查的旗標。如果廣告指令碼本身被攔截、沒能載入,這個全域變數就永遠不會被定義。網頁會在指令碼標籤本該執行完的那一刻檢查它是否存在;undefined 就代表指令碼從未執行過。
這三種方式的思路是一致的:先設定一個只有在沒人干擾的情況下才會成立的預期,再檢查它是否成立。它們都沒有直接向瀏覽器提出一個問題,因為根本沒有這樣一個問題可問。
為什麼它對每一種攔截器都同樣有效
有人可能會覺得,瀏覽器擴充和內建的、沒有任何擴充的攔截器,在偵測者眼裡應該完全不同。但實際上並非如此,原因在於兩者是透過不同機制在執行同一套過濾規則。
傳統的廣告攔截擴充會注入內容指令碼和樣式表,隱藏或移除符合的元素,也能直接攔截網路請求。宣告式攔截引擎的底層做法不同,結果卻殊途同歸:瀏覽器事先拿到一份編譯好的規則清單,由自己負責執行,不需要攔截器本身的程式碼逐頁檢查。WebKit 的內容攔截器就是這樣運作的——它最早為 Safari 引入,如今是 iOS 上每一個內容攔截類 App 的基礎:App 提交一份 JSON 規則清單,WebKit 就會在引擎層面把它套用到此後載入的每一個頁面上。這些規則既能攔截資源,也能按 CSS 選擇器隱藏元素(css-display-none 動作),所以網路類偵測和誘餌偵測它都能觸發。Chrome 的擴充平台也在往類似方向演進:declarativeNetRequest 讓擴充把靜態或動態的規則集直接交給瀏覽器,而不是用 JavaScript 檢查每一個請求;在 Manifest V3 下,這是 Chrome 要求網路攔截類擴充採用的模型。(MV3 攔截器隱藏元素時仍要靠注入 CSS,但背後用的是同一批過濾清單。)
機制不同——一邊是由引擎執行的編譯規則清單,一邊是盯著頁面的指令碼——但網頁能觀察到的結果是一樣的:符合過濾規則的誘餌元素被隱藏或刪除,符合過濾規則的請求載入失敗。一個圍繞結果而不是機制打造的偵測器,不需要知道也不需要在意自己面對的是哪一種攔截器。這也是為什麼一個「僅僅」是 Safari 內容攔截器、既不能執行指令碼也讀不到頁面內容的行動裝置廣告攔截器,依然會觸發和桌面擴充一模一樣的誘餌元素偵測。
偵測結果洩露了什麼
單看一次這類偵測的正向結果,幾乎只是一個位元:是的,這個頁面上有東西在過濾,或者不是。這一個位元對網站本身已經有用——它是觸發反廣告攔截彈窗、或者「請把我們加入白名單」橫幅的開關——但單獨來看,算不上什麼強指紋訊號,因為太多訪客給出的答案都一樣。
真正讓它變得更精細的,是同時執行不止一個誘餌元素,每一個都按不同過濾清單的慣例設計。一個偵測器同時放出五個誘餌 div——一個仿照 EasyList 的通用規則、一個仿照某個地區性清單的慣例、一個仿照只有更嚴格的「惱人元素」清單才有的規則——並分別檢查每一個,得到的就不只是「被攔截:是」這一個結論了。它還能知道具體是哪些模式被命中,而不同使用者訂閱的過濾清單組合各不相同,這個具體組合就能收窄你所融入的人群範圍。這和任何指紋訊號背後的熵計算是同一回事:一個罕見的結果組合比一個常見的組合更具辨識度,我們關於指紋熵與匿名集的指南對通用訊號講的正是這個道理。廣告攔截偵測只是同一個思路下一個熵值很低的小例子——值幾個位元,算不上單獨的身分標識。
有必要說清楚這項技術不是什麼。它和探測你到底裝了哪些具體擴充不是一回事——後者靠的是探測某個擴充自己暴露的資源檔案,或者它在頁面上留下的獨特副作用,是一種瞄準某個具名擴充的更精準技術。廣告攔截偵測不需要知道是哪個產品在起作用,它只需要知道有什麼東西干擾了它設下的誘餌。
誤判:沒裝任何東西也可能被判定為「已攔截」
因為這種偵測盯的是結果而不是一個直接的問題,任何能產生同樣結果的東西都會觸發它——而好幾種和擴充毫無關係的常見設定恰好都會這樣。不過要分清觸發的是哪一種偵測:作用在網路上的東西,能讓「請求被攔截」和「全域變數缺失」兩種偵測示警,卻碰不到網頁在本機建立的誘餌元素。
- **網路層級的廣告攔截。**家裡的 Pi-hole、帶過濾功能的 DNS 解析器,或者路由器層級的廣告攔截,會讓廣告請求根本到不了廣告伺服器——裝置上沒裝任何擴充,廣告指令碼卻照樣載入失敗、全域變數也不會出現,和被擴充攔截時一模一樣。
- **瀏覽器自帶的追蹤保護。**Firefox 的「強化追蹤保護」預設會在隱私視窗中封鎖已知的追蹤內容,切到「嚴格」模式後則對所有視窗生效,全程不需要任何附加元件。如果偵測器載入的「廣告」恰好位於它所用追蹤清單裡的網域上,這次請求同樣會失敗。
- **內建廣告攔截的瀏覽器。**Brave 的 Shields,以及 Opera、Vivaldi 等瀏覽器的內建攔截器,會自己執行過濾清單。視設定而定,其中可能包括元素隱藏規則——所以即便什麼都沒裝,它們也可能觸發誘餌元素偵測。
- **企業或學校代理伺服器。**很多受管理的網路會把流量路由經過一個帶過濾功能的代理伺服器,作為更廣泛內容政策的副作用,順帶剝離掉廣告和追蹤網域,這和使用者自己安裝了什麼毫無關係。
- **能識破偽裝追蹤器的 DNS 解析服務。**一些過濾型 DNS 服務更進一步,會順著 CNAME 記錄去識別藉 CNAME 偽裝的追蹤器——也就是藏在看似第一方子網域背後的第三方追蹤器。這樣一來,即便某個廣告或追蹤呼叫的網址看起來是無害的第一方位址,也可能被攔下。
這些情形都不涉及已安裝的擴充,但每一個都能讓網頁得出「偵測到廣告攔截器」的結論。如果網站把這個訊號當成已安裝某個擴充的確鑿證據,那就是在做資料本身並不支持的推論——它實際知道的,只是從頁面到廣告伺服器之間的某個環節出現了干擾。
偵測結果沒能告訴網站什麼
有必要把邊界講清楚,因為很容易高估一次誘餌偵測的通過或失敗究竟證明了什麼。執行這項測試的網頁並不會知道你到底裝了哪個擴充(如果有的話)——那需要我們關於擴充隱私的指南裡講到的、更有針對性的擴充探測技術。它也不會知道你的身分或瀏覽歷史。而且,正如上面的誤判清單所示,僅憑一次失敗的請求,它無法區分擴充、網路層過濾、瀏覽器的嚴格隱私設定,還是受管理網路的代理伺服器。它頂多能知道的,是這一次頁面載入中,哪些可觀察的模式受到了干擾——數量很有限的幾個位元。
這篇文章講的是這種推論是怎麼建構出來的、它能證明什麼、不能證明什麼——不是一份規避偵測的指南。過濾清單作者和反廣告攔截廠商之間有一場持續的攻防,那不在本文討論範圍內;上文也沒有任何隱藏攔截器、繞過反廣告攔截彈窗的方法。
看看你自己的設定洩露了什麼
BrowserInsight 的外掛與擴充偵測會在擴充偵測的同時執行一個誘餌元素測試,你可以藉此親眼看看,網站在偵測你的瀏覽器時到底能看到什麼。由於這項測試屬於外觀層偵測,Pi-hole 這類只在網路層運作的攔截器不會在這裡顯示出來。如果結果讓你意外——明明沒裝擴充卻顯示被攔截,或者明明裝了卻沒被攔截——可以看看是不是用了內建攔截器的瀏覽器,或者擴充的元素隱藏功能被關閉、目前網站被加入了白名單。想了解這類偵測在完整瀏覽器指紋裡處於什麼位置,可以看看指紋偵測,它展示了網頁能觀察到的關於你的設定的其餘資訊。
常見問題
網站能準確知道我用的是哪個廣告攔截器嗎?
不能,至少像誘餌元素或失敗請求這樣基於結果的偵測做不到。這類測試只能確定有什麼東西干擾了廣告類內容或請求——它們無法識別具體是哪個擴充或哪種攔截機制在起作用。要指認某個具體擴充,需要一種針對該擴充自身痕跡的、更有針對性的技術。
這在手機上和電腦上的原理一樣嗎?
對於宣告式攔截器來說是一樣的。一個基於 WebKit 內容攔截器 API 打造的 iOS 內容攔截類 App,會產生和桌面瀏覽器擴充相同的可觀察結果——誘餌元素被隱藏、廣告請求失敗——即便它根本沒有能力在頁面上執行指令碼。偵測器不需要知道具體是哪種機制在起作用。
我明明沒裝廣告攔截器,也可能被判定為「已攔截」嗎?
會的。網路層過濾(家用 DNS 攔截器、企業代理伺服器)和瀏覽器內建的追蹤保護,可能讓「請求被攔截」或「全域變數缺失」類偵測示警;而 Brave 這類內建廣告攔截的瀏覽器,連誘餌元素偵測也可能觸發——這些情形都不需要安裝任何擴充。
被偵測到裝了廣告攔截器,隱私風險大嗎?
單獨來看是個很小的訊號——比起一個指紋,更接近一個位元。只有當網站同時放出好幾個樣式各異的誘餌元素、並記錄具體哪些被攔下時,它才會變得更有辨識度,因為這種命中模式在不同使用者之間的差異,比單純的「是/否」答案大得多。

