公开网页能悄悄向 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、物联网设备上那种绑定在本地的管理面板,往往是基于一个默认前提搭建的:只有已经身处局域网内的人才能访问到它。它没有理由预料到,会有请求是被一个毫不相干的公开网站上的 JavaScript 伪造出来的。Chrome 在解释这项新权限的用途时,一句话就点出了本文讨论的两个方面:它要"保护用户免受针对路由器及私有网络上其他设备的跨站请求伪造(CSRF)攻击,并削弱网站利用这类请求对用户本地网络进行指纹识别的能力"。同一个请求,攻击者能拿它做两件不同的事。
应对历程:先是预检请求,然后才是权限提示
Chrome 第一次尝试堵上这个漏洞时用的是 Private Network Access(私有网络访问,PNA),在了解取代它的方案之前,先弄清楚它为什么没能完全奏效是值得的。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 对它管辖范围的定义,比"任何发往本地地址的请求"要窄:所谓本地网络请求,指的是从公网发往本地网络或环回地址的请求。这涵盖了 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?
因为它最初的设计假定本地设备能够被更新,从而学会应答一种新的请求,而现实中大多数消费级路由器、打印机和物联网设备出厂之后基本上再也不会被更新。强制执行的日期被一年又一年地推迟,Chrome 最终才认识到,这项修复必须完全依托浏览器本身和用户的决定,而不能指望永远不会到来的硬件端配合。
结语
一个公开网页伸手探入 127.0.0.1 和你路由器的局域网地址,并不是什么假设情形——这是你的浏览器会欣然发出的请求,计时精细到足以描绘出有什么在监听,却完全不需要读取哪怕一个字节的响应内容。Private Network Access 曾试图通过征询那些够不着的本地设备的许可来堵上这个缺口,结果花了好几年才发现这条路走不通。Local Network Access 转而征询你的许可,这项权限提示已随 Chrome 142 上线。即便你自己从未见过这个提示,了解这套机制依然是有意义的:它又一次证明了一个看似与你的身份毫无关系的浏览器功能——一次原始的网络请求——最终却能像任何指纹识别脚本一样,精确地描绘出你的设备和你的网络。
推荐阅读:


