公開網頁能悄悄向 127.0.0.1 和你的路由器發請求,用回應快慢摸清你區域網路上跑著什麼。Chrome 142 的本機網路存取權限終於擋下了它。
一個開在普通公開網站上的普通分頁,就能要求你的瀏覽器向 127.0.0.1、以及你路由器的區域網路位址(比如 192.168.1.1)發送請求,並計算每一次得到回應要花多久。它完全不需要讀取回應內容——跨來源規則早就擋住了這條路。它只需要知道對方回應得快、回應得慢,還是根本沒有回應。光憑這一點,就足以描繪出你這一個公開 IP 位址背後,網路上究竟正在執行些什麼——而在不久之前,瀏覽器裡沒有任何機制能阻止一個網頁這麼做。
重點摘要
- 公開網頁可以用一般的 JavaScript 向
localhost與192.168.x.x之類的私有區域網路位址發送請求——fetch()、一個<img>標籤,或是一條 WebSocket 連線都做得到,而且預設情況下都不需要任何特殊權限。 - 網頁根本不必讀取回應內容。單憑計時(有沒有回應、回應多快)與連線結果(成功建立、被拒絕、還是逾時),就足以判斷某個本機位址與連接埠上是否有東西在監聽。
- 這裡外洩的是關於你的機器與網路的事實,而不是你的瀏覽器:某個 3000 埠上的開發伺服器、一台媒體伺服器、某廠商的桌面代理程式,或是特定型號的路由器——這些事實穩定、難以更改,而且辨識你的方式與 Canvas 或字型指紋截然不同。
- 同樣形式的請求,也是針對路由器與本機管理面板的 CSRF(跨站請求偽造)攻擊手法——這些裝置從來就沒設計成要應付來自陌生公開網頁的請求。
- Chrome 的解法「本機網路存取權限(Local Network Access)」,是一個在 Chrome 142 上線、面向使用者的權限提示——它取代了先前更低調的嘗試「私有網路存取(Private Network Access)」,後者徵求的是本機裝置的同意,而不是使用者本人。
一個網頁實際上能做到什麼
這一切根本不需要什麼罕見的瀏覽器 API。想要探測你網路的網頁,只要發出一批普通的請求就行——對 http://127.0.0.1:3000/ 發出一次 fetch() 呼叫、把一個圖片元素指向 http://192.168.1.1/,或是開啟一條指向 ws://127.0.0.1:8080 的 WebSocket 連線——然後觀察接下來會發生什麼。同源政策確實擋住了網頁讀取回應內容,但它擋不住網頁得知一個 fetch() 的 promise 究竟是 resolve 還是 reject、一個圖片元素觸發 load 或 error 事件花了多久,或是一條 WebSocket 最終走到 onopen 還是 onerror。只要對 127.0.0.1 與私有位址範圍(192.168.0.0/16、10.0.0.0/8)上一系列常見連接埠批次計時,成功、拒絕與逾時交織出的模式本身,就是掃描結果——回應內容一個位元組都不必離開這個本機網路。
外洩的是什麼:關於你機器的事實,而不是你瀏覽器的事實
BrowserInsight 在指紋辨識底下談的多數東西——Canvas 輸出、已安裝字型、WebGL 渲染器字串——描述的都是你瀏覽器與其渲染堆疊。本機網路掃描描述的是不同的東西:這台實體機器、以及它所在的網路區段,實際上正在跑些什麼軟體。3000 埠或 8080 埠上有回應,通常代表有個開發者的本機伺服器在跑。某個裝置在你路由器的位址上,以符合已知管理面板特徵的方式回應,就能辨識出路由器型號,有時甚至能細到韌體家族。某個連接埠上有回應,而該埠正是特定廠商的桌面同步代理程式或媒體伺服器所綁定的埠,就能辨識出那套軟體是否存在於你的區域網路上——無論它此刻是否正在你看著的這個分頁裡運作。這些訊息都不是來自某個標頭或 JavaScript API 直接告訴網頁「使用者裝了 X」,而是純粹從哪些本機的門對敲門有所回應推論出來的。這使它成為一種與這個網站的指紋技術指南通常討論的指紋熵截然不同的訊號,也有著不一樣的匿名集形狀:它辨別的與其說是你跟造訪同一頁面的其他訪客有何不同,不如說是描繪出你家裡或辦公室這個特定網路的樣貌。
問題的另一半:針對你路由器的 CSRF
指紋辨識並不是這個問題被修補的唯一原因。同樣形式的請求——一個公開網頁伸手碰觸一個從未邀請過它的私有位址——也正是針對路由器與本機管理面板的跨站請求偽造(CSRF)攻擊的運作方式。路由器的網頁管理介面,或是 NAS、IoT 裝置那種只綁定在本機的管理面板,往往是基於一個默認前提打造的:只有已經身在區域網路內的人才可能連得上它。它完全沒有理由預期會收到一個由完全不相關的公開網站上的 JavaScript 偽造出來的請求。Chrome 在說明這項新權限的用途時,一句話就同時點出了本文談的兩件事:它要「保護使用者免於針對路由器與私有網路上其他裝置的跨站請求偽造(CSRF)攻擊,並削弱網站利用這類請求對使用者本機網路進行指紋辨識的能力」。同一個請求,攻擊者可以拿它做兩件不同的事。
緩解手段的歷史:先有預檢,後有權限提示
Chrome 第一次嘗試補上這個漏洞,用的是「私有網路存取(Private Network Access)」——在看它後來被什麼取代之前,值得先了解它為什麼沒能完全奏效。PNA 最核心的構想,是把公開網頁對私有網路目標發出的請求擋在一次 **CORS 預檢(preflight)**之後:在真正的請求送出之前,瀏覽器會先送出一個 OPTIONS 請求,要求目標裝置明確表態選擇加入,做法是回應一個 Access-Control-Allow-Private-Network: true 標頭。原則上,這把決定權交給了掌控那台本機裝置的人——這是個合理的設計。但實務上,它始終沒能走出實驗階段。Chrome 的預檢說明文章記錄了它在 Chrome 98 中浮現問題後被回退,接著在 Chrome 104 以一種刻意「沒有牙齒」的形態回歸:預檢失敗只會在開發者工具裡印出一則警告,真正的請求照送不誤,而且預檢本身的逾時被限制在 200 毫秒以內,以免拖慢頁面載入。真正的強制執行被排到「最早 Chrome 113」,而且明確以相容性資料顯示夠安全為前提。它始終沒有落地——Chrome 自己的本機網路存取公告就記錄了預檢這條路被擱置。
PNA 的另一半也沒好到哪裡去。Chrome 的 PNA 更新公告記錄了這項工作的另一條線——禁止不安全的公開網頁發出私有網路請求——如何從 Chrome 92 延到 93、在收到更多開發者回饋後又延到 94;而讓受影響網站得以繼續運作的棄用試用期,則被一延再延(先延到 113、再延到 116),直到 Chrome 117 才終於到期。
這正是 PNA 教會大家的核心教訓:當多數人網路上的裝置——路由器、印表機、智慧家庭中樞、網路儲存裝置——永遠等不到那個能教會它們表態同意的韌體更新時,向目標裝置徵求同意這條路根本走不通。期限往後延幾次都改變不了這件事,因為問題根本不在期限那一端。
為什麼在這裡權限提示勝過預檢
本機網路存取權限(Local Network Access,LNA)是 Chrome 給出的答案,它把決定權從那台永遠連不上的裝置,轉移到真正坐在鍵盤前的人身上。瀏覽器不再要求路由器或開發伺服器透過一個它永遠不會送出的標頭來表態同意,而是在公開網頁第一次試圖連到某個私有位址時,直接跳出提示問使用者——Chrome 官方的提示文案是「尋找並連線到你本機網路上的任何裝置」。如果使用者信任這個網站(比如某個智慧家庭儀表板確實需要這項功能),可以選擇允許一次;而對絕大多數根本沒有正當理由去探測家用網路的網頁,使用者只需要拒絕就好。這徹底翻轉了瓶頸所在:Chrome 不必再苦等數以百萬計、沒人維護的裝置去實作一個新標頭,只需要讓瀏覽器自己去執行這項檢查,而每一次都由擁有這個網路的人親自拍板。
這項功能已隨 Chrome 142 上線;在那之前,從 Chrome 138 起你就可以透過 chrome://flags#local-network-access-check 這個開關自行啟用。Chrome 對它管轄範圍的定義,比「任何發往本機位址的請求」要窄:所謂本機網路請求,指的是從公開網路發往本機網路或回送位址(loopback)的請求。這涵蓋 RFC 1918 私有位址範圍(例如 192.168.0.0/16)、鏈路本地位址(169.254.0.0/16 與 fe80::/10)、IPv6 唯一本地位址(fc00::/7)、回送位址(127.0.0.0/8 與 ::1),以及 .local 主機名稱——恰恰就是掃描腳本會逐一掃過的那片位址空間。目前還管不到的,是一個本身就跑在私有位址上的頁面繼續往內探到回送位址;Chrome 表示日後計畫把這項保護擴大到所有發往本機網路的跨來源請求。
讀者今天會看到什麼
如果你用的是近期版本的 Chrome,網站嘗試連到你區域網路上的某個位址時,你看到的會是權限提示,而不是一個悄悄送出的請求。你造訪的多數網站永遠不會觸發它,因為它們沒有理由跟你的路由器或本機開發伺服器對話——如果有某個網站觸發了,而你想不出為什麼,拒絕就是最安全的預設選擇。這是 Chrome 主導的推行,目前還不是普遍適用的保護:別因為在一款瀏覽器上看過這個提示,就假設每個瀏覽器都有同樣的提示或同樣的保護。如果你想知道自己目前的瀏覽器與網路設定,除了這個特定機制之外還暴露了些什麼,BrowserInsight 的指紋檢測涵蓋了網頁完全不需要任何權限提示、就能讀到的更廣泛一批機器與瀏覽器層級訊號。
這篇文章在站內其他隱私議題裡的位置
很容易把這個議題,跟站內其他剛好放在附近頁面、但其實不相關的追蹤技術混為一談,所以有必要把界線說清楚:CSS 指紋技術是樣式表推斷裝置特徵(例如深色模式或指標裝置類型),完全不涉及網路請求。工作階段回放是腳本記錄你在已經打開的頁面裡面做了什麼。瀏覽器擴充功能隱私談的是已安裝的附加元件讀取頁面資料,而不是瀏覽器本身變成網路掃描器。持久訪客 ID談的是跨次造訪重新辨識你,而不是描繪你的區域網路地圖。這篇文章談的則是特定的一件事:瀏覽器被當成探針,用來刺探這台機器所在的私有網路——這是與前面四種截然不同的機制,儘管它們都共享著同一個大主題:網頁得知了比你自願透露的還要多的訊息。
常見問題
這會影響每一款瀏覽器,還是只有 Chrome?
本機網路存取權限是一項 Chromium 功能,於 Chrome 142 上線。其他引擎的支援情況各不相同、也會隨時間變化,所以請查閱你使用的瀏覽器的具體發行說明,而不要假設這項保護放諸四海皆準——底層的請求手法(fetch、<img>、WebSocket)在任何瀏覽器上運作方式都一樣,除非那款瀏覽器特地加以限制。
網站能在我完全沒察覺的情況下掃描我的網路嗎?
在本機網路存取權限出現之前,答案是可以——一支探測數十個本機位址與連接埠的腳本完全不會產生任何看得見的介面;唯一留下的痕跡在瀏覽器開發者工具的網路面板裡,而幾乎沒有人會去查看那裡。比起技術機制本身,正是這種「完全無聲」讓這件事值得用一個權限提示來修補,而不是放任每個網站憑良心自律。
這跟 WebRTC 洩漏我的本機 IP 位址是同一回事嗎?
不是,儘管兩者都牽涉到你的本機網路。WebRTC 的 ICE 候選位址蒐集過程,可能在建立點對點連線的副作用下洩漏你的本機 IP 位址。而這裡談的不同:網頁是主動對你網路上的位址發送請求,並讀取計時與結果,而不是在設定連線的過程中順帶洩漏了你自己機器的一個位址。
我需要做什麼才能受到保護嗎?
如果你用的是目前版本的 Chrome,這個權限提示預設就是開著的——沒有什麼設定需要你去開啟。只要在那些沒有明顯理由要跟你本機網路對話的網站上拒絕這個提示就好,就跟你對待任何一個與網站宣稱功能對不上的權限請求時一樣。
為什麼私有網路存取(Private Network Access)花了這麼久才變成本機網路存取權限(Local Network Access)?
因為它最初的設計假定本機裝置可以被更新到能回應這種新型態的請求,而多數消費級路由器、印表機與 IoT 裝置在出貨之後實際上幾乎從未再被更新過。強制執行的期限一次又一次被往後延,最後 Chrome 才做出結論:這個修補必須完全活在瀏覽器與使用者的決定裡,而不能仰賴那些永遠不會到來的硬體端配合。
結語
一個公開網頁伸手碰觸 127.0.0.1 與你路由器的區域網路位址,不是什麼假設情境——這是你的瀏覽器會欣然送出的請求,計時得剛剛好足以描繪出有什麼在監聽,卻完全不需要讀到回應裡的任何一個位元組。私有網路存取試著透過向那些連不上的本機裝置徵求許可來補上這個缺口,結果花了好幾年才發現這條路根本行不通。本機網路存取權限改成直接問你,用一個在 Chrome 142 上線的權限提示。就算你自己從沒見過這個提示,這個機制依然值得了解:它又一次證明了一個看起來與你的身分毫不相干的瀏覽器功能——一個單純的網路請求——最終竟能像任何指紋辨識腳本一樣,精準描繪出你的機器與你的網路。
延伸閱讀:


