Bromure 如何在页面看见广告之前就把它拦下
大多数广告拦截器都是浏览器扩展程序,而大多数浏览器扩展程序,又恰好与它要保护你免受其害的那个页面,运行在同一个进程里。Bromure 的做法不一样。下面讲讲它是怎么做的,以及为什么这很重要。
一款活在浏览器里的广告拦截器,就像是在擂台上当裁判。Bromure 把裁判搬到了网络本身——那里页面够不着,脚本碰不到,任何「反广告拦截」横幅也跟它争不起来。
广告拦截是当今浏览器最受欢迎、却偏偏不随浏览器一起发行的功能。成百上千万人在安装浏览器的第一天,就装上了一个第三方扩展,因为现代的网络,如果没有它,简直无法正常使用。这件事本身,就是一股设计上的怪味。但几乎所有广告拦截器的工作方式,都值得我们多看一眼,因为它们的架构其实非常关键——既关乎拦截效果本身,也关乎当页面主动反击时,会发生什么。
本文将介绍一款广告拦截器通常是怎么工作的,这种形态又有哪些现实的局限,以及 Bromure 为何选择另一条路——它的核心思路,借鉴自一件你也许在某位朋友家地下室的 Raspberry Pi 上见过的工具。
广告不只是烦人。它是一个加载问题,一个隐私问题,更是一条攻击通道。
用过几年广告拦截之后,人们很容易忘了「未经过滤」的网络本来是什么样子。一篇热门新闻文章,在不加任何过滤的情况下打开,通常会从 30 到 80 个第三方域名加载资源。其中绝大多数是分析代码、追踪像素,以及实时竞价的接口。它们让页面变重,让加载变慢,还替一堆你从未听说过的公司往你电脑里塞 cookie,而且——正如本月早些时候那篇关于勒索软件的文章所谈到的那样——它们也是恶意软件一条悄无声息的投递通道,因为广告网络时不时就会被骗,在正规网站上分发恶意素材。
所以,拦下这些请求并不是个外观问题。它是一次性能上的胜利,一次隐私上的胜利,而且——因为广告网络正是恶意软件的一条常规分发通道——它还是一次实实在在的安全胜利。唯一的问题只是:怎么拦。
一款典型的浏览器扩展是如何拦广告的。
几乎所有主流的广告拦截器,包括你大概率装过的那些热门产品,都是浏览器扩展程序。这意味着,从架构上讲,它活在浏览器里——它在浏览器进程中运行 JavaScript,针对浏览器的网络 API(webRequest、declarativeNetRequest)注册回调,逐一检查浏览器即将发出的每一个请求。
这种形态确实能用——毕竟大多数人每天就靠它——但它在结构上有几个实打实的局限:
页面能察觉到
由于扩展和页面跑在同一个进程里,页面自己的 JavaScript 就能去探测它:「我那个请求真的发出去了吗?这个变量到底有没有被定义?」不少网站(尤其是绝大多数付费墙新闻网站)会检测缺失的请求,没有就拒绝加载,要求用户「先关掉广告拦截器」。
页面和它处在同一信任域内
扩展和它要过滤的那个恶意页面,共享同一个浏览器进程、同一个渲染器,有时候还共享同一套权限。页面里一个足够严重的浏览器漏洞,会把扩展和其他一切一并端掉。扩展是代码;扩展也是攻击面。
请求已经被生出来了
当扩展看到一个请求时,浏览器其实早就决定要发了——URL 已经解析过,(看具体的 API)DNS 查询可能也已经做了。在这一步拦,是过滤,而不是预防。
过滤 API 正在越变越弱
Chrome 的 Manifest v3 改造限制了过滤扩展能做什么,主要做法是限制规则条数,并用 declarativeNetRequest 替代动态的 webRequest。扩展 API 的架构,是由浏览器厂商决定的——不是由你决定的。
Bromure 是怎么做的:那堵墙不是扩展。
Bromure 的广告拦截器并不是一个扩展。浏览器本身完全没有任何广告拦截代码。真正扮演广告拦截器角色的,是浏览器所运行的那个一次性 Linux 虚拟机里的网络栈——而从浏览器的角度看,广告域名干脆就没法解析。
从机制上讲,真正干活的是两块又小又无趣的软件:
一个本地 HTTP 代理(Squid)
浏览器发出的每一个外发请求,都会被透明地路由到虚拟机里运行在 localhost:3128 上的 Squid 代理。Squid 只是管道:它不决定什么是广告,只负责替浏览器做 DNS 解析和请求转发。
一个本地 DNS 解析器(dnsmasq)
Squid 被配置为通过 127.0.0.1 上的本地 dnsmasq 实例来解析每一个域名。dnsmasq 的配置里包含了一份大约 10 万个域名的屏蔽列表——广告网络、追踪器、分析服务、以及已知的恶意主机。名单上的域名都会被解析到 0.0.0.0,哪里都去不了。
最终的结果是:从浏览器的角度看,它连上的是一个「看起来像互联网」的网络,只不过广告和追踪类的域名在 DNS 里根本没有记录。浏览器向 doubleclick.net 发起查询,拿到「无路可走」,于是就放弃了。不会打开任何连接,不会尝试任何 TLS 握手。哪家的 cookie 也送不出去,因为压根就没有一个「送出去的目的地」。
这套思路,其实已经在人们家里地下室里跑了很多年了。
如果你曾经搭过 pi-hole——那台很多人放在 Raspberry Pi 上,用来替整个家庭网络过滤广告的小盒子 DNS 黑洞——你会对 Bromure 的架构感到格外眼熟。底层用的屏蔽列表,正是 Steven Black 整理的那份统一 hosts 文件,也是多年来那些家庭部署一直在用的那份。思路上完全一样:不去试图把广告从页面里剥掉,而是干脆拒绝告诉浏览器广告服务器在哪里。
关键的区别在于,黑洞放在哪里。一个覆盖整个家庭的黑洞,保护的是家里的每一台设备;而一个 Bromure 的黑洞,只保护一个浏览器配置档案。你可以在看新闻用的那个配置档案上把它打开;在银行宁愿你不要走任何奇怪中间件的那个配置档案里,把它关掉。
这到底能给你带来什么。
实打实的好处一条接一条:
被攻陷的页面无法将其关闭
屏蔽列表不是浏览器加载的代码,它是浏览器所在的那张网络。一个渲染器漏洞、一段恶意脚本、一个行为不端的第三方库——它们谁也碰不到 dnsmasq 的配置,因为它们谁也冲不出浏览器进程、抵达不了虚拟机的网络栈。
页面看不穿它
页面里的 JavaScript 可以整天检查 navigator 属性,它就是没法知道所在网络的 DNS 配置——因为这本来就不是 JavaScript 被允许知道的事情。在页面看来,一个被拦下的广告,和一个宕机了一分钟的广告域名,看起来一模一样。
没有扩展带来的攻击面
广告拦截扩展不止一次被攻陷过——可能是通过扩展商店,可能是因为被收购,也可能是因为一次恶意更新。Bromure 里没有这样一个扩展可以被攻陷,因为根本就没有扩展。
按配置档案启用,像其他能力一样可切换
广告拦截是一个按配置档案的设置(「隐私与安全」里的「拦截广告」),默认关闭,你想在哪个配置档案里开就在哪个里开。你的银行档案保持不做过滤;你每天看新闻的那个档案则干干净净。
坦诚说说它的局限。
这一领域里没有白吃的午餐。在网络层这种做法里,有几件事是它有意不去做的,你也应该事先心里有数:
不做外观过滤
传统的扩展可以把原本放广告的那块空白盒子藏起来。Bromure 不会去改写页面,所以如果网站为一则 728×90 的广告预留了位置,那块位置就会留着,空在那儿。在少数几个网站上,这是一个轻微的外观小瑕疵;而代价换来的,是页面里什么都没有留给脚本去察觉。
第一方广告还会留下
如果一个网站从自己的域名上投放自家的广告(一些大型科技平台现在就这么做),域名层面的拦截就没法区分「文章」和「广告」——它们都挂在同一个主机名下。这是一个实打实的缺口;在真正重要的场景下,解法是在上面再加一层内容脚本层,这也是我们正在研究的方向。
反广告拦截墙仍能察觉到缺失
有些网站根本不去找广告拦截器在哪。它们只检查某个特定的脚本有没有加载,没加载就拒绝展示文章。对这类网站来说,任何拦截器——无论是扩展还是网络层的——都会被挡下。这是一场和发行方之间的政策之争,而不是和拦截器之间的技术之争。
屏蔽列表随 Bromure 一同分发
gravity.list 在镜像构建时就烘进去了,并随 Bromure 的更新而更新。它不像家里那种专门一直开着的黑洞那样,每小时就去拉最新的规则。对一份基于主机名的名单来说,这通常够用了——广告域名的更替速度远不如恶意软件域名——但这一点值得知道。
事物的形状。
更宽泛一点说,重点并不是 Bromure 的广告拦截器在匹配规则这件事上比 uBlock Origin 更神。事实上,uBlock 的规则引擎要精巧得多。真正的重点,在于拦截发生在哪里。扩展是一个礼貌的裁判,在恳求浏览器别去加载这个东西。Bromure 则是那条请求本该走的路,在司机还没上车之前,就悄悄把高速路的入口封了。
密封虚拟机里的广告拦截并不是 Bromure 的一项新功能;它只是这套架构从第一天就在那里的一个自然结果。每一个配置档案的网络层,都是它自己的一个小世界。这个世界里可以有 WARP,可以有付费的 VPN,可以有 Tor 传输。而它也可以——免费地——拥有那套多年来一直在人们家里地下室里悄悄过滤广告的 DNS 黑洞,已经替你安装好、配置好,并严格限定在你想让它生效的那个浏览器会话里。
如果你想看长版本:那份屏蔽列表只是一个纯文本文件,随虚拟机镜像一起发放——下一个版本发布时,你可以亲自翻开看看,一条一条读完那十万个即将在你浏览器里悄悄不复存在的域名。如果你只想要短版本:在任何一个 Bromure 配置档案里打开「隐私与安全」,把「拦截广告」打开就行。路已经封好了。你只管开车。