DNS 泄露防护:解析路径、代理与泄露测试
说明使用代理时 DNS 泄露如何暴露浏览活动、常见缓解方法各自留下的缺口,以及如何用泄露测试核对解析路径。
BotBrowser Team
代理会改变浏览器页面流量看上去的来源,但浏览器在建立连接之前,仍然要把每个主机名解析成地址。如果这次查询发给了代理路由之外的解析器,那么即使页面本身是通过代理加载的,解析器也会知道请求了哪些域名,以及请求来自哪个网络。这个缺口就是 DNS 泄露,也是代理配置最常出现不一致的原因之一。BotBrowser 可以在测试期间让受控上下文的代理和 DNS 设置保持一致,但不能控制该浏览器之外的解析器或网络。
要解决它,关键在于归属:每一次查询由谁来解析域名,这个解析器位于哪里,谁能读取它的日志。下面依次回答这些问题,涵盖 SOCKS5、SOCKS5H 和 HTTP CONNECT,比较常见缓解方法及其各自留下的缺口,最后给出一套把任何不一致都视为检查失败的泄露测试流程。
TL;DR
socks5://可能在本地解析;socks5h://会把主机名交给代理解析。- 页面加载干净并不能证明
prefetch、dns-prefetch、IPv4 和 IPv6 使用了预期的解析器。 - 记录预期的运营方和地区,把任何无法解释的解析器视为失败。
Contents
谁来解析主机名
MDN 的 DNS 词条说明了把域名映射到 IP 地址的这套系统。浏览器必须先完成这个映射才能打开连接,所以每次导航、每个嵌入资源和每个后台请求都会遇到同一个问题:由哪个组件执行查询。路径中有代理时,候选者不止一个,由代理的协议方案决定谁来回答。
三种常见安排的行为各不相同:
- SOCKS5。 浏览器用本地解析器解析主机名,再把 IP 地址交给代理。查询发生在代理路由之外,因此本地网络的解析器能看到域名。
- SOCKS5H。 浏览器把主机名本身交给代理,由代理解析。经由这条连接传递的域名不会再询问本地解析器。
- HTTP CONNECT。 浏览器在隧道请求中写明目标主机,由代理服务器去连接它。目标的解析通常发生在代理一侧。
除了代理方案,还有第四种情况:浏览器可以用自己的解析器,而不是操作系统的解析器来解析域名。这个选择改变的是向哪个解析器发问、该解析器联系哪些服务器,但它本身并不能说明得到的答案是用于直连,还是交给代理。请把浏览器侧解析和代理侧解析视为两个独立的开关,并分别记录各自的设置。
SOCKS5 规范 RFC 1928 允许客户端向代理发送地址或域名。浏览器和命令行工具使用的 socks5h:// 方案是客户端的约定,用来选择后一种形式。请按字面理解配置中的方案:socks5:// 和 socks5h:// 是两个不同的决定,只差一个字母,就会把查询从你的机器挪到服务商那里。
把查询挪到代理并不等于它就是私密的。代理一旦解析域名,由服务商决定哪个上游解析器应答、该解析器位于哪个地区、答案是否被缓存或过滤,以及记录什么。这些选择因服务商而异,不在你的控制范围内,而且大多数服务商不会以你能核对的形式公布。代理配置文章更详细地介绍了这些方案,但仅凭方案标签永远不能证明查询去了哪里。唯一的证据来自验证。
泄露会暴露什么
你本地网络或网络服务商的解析器,能看到它被要求解析的域名、每次查询的时间,以及发起查询的网络地址。如果页面流量走代理而域名在本地解析,那么即使解析器从未看到页面内容,也仍然可以还原出一份访问过的站点列表。HTTPS 保护的是页面,并不会隐藏它之前的 A 或 AAAA 域名查询。
第二种暴露是地理位置上的。解析器部署在特定地区,许多网络会分配离用户较近的解析器。如果代理出口在德国,而查询来自服务你所在地区的解析器,这两个信号就不一致。某个具体服务是否会比较它们因情况而异,没有人能保证会或不会。出现不一致仍然应该被视为配置存在矛盾,因为这条路由的目的就是让各个信号彼此一致。
第三种是你无法控制的日志。解析器可能保留它应答过的域名记录。把浏览器指向另一个解析器,只是改变了谁持有这些记录,并没有让记录消失。所以真正有用的问题从来不是 DNS 是否被隐藏,而是哪个运营方能看到这些域名、位于哪个地区,以及它是不是你选择的运营方和地区。
地址族同样重要。同时能使用 IPv4 和 IPv6 的浏览器,可能通过代理路由未覆盖的地址族访问目标,所以只检查一种地址族的测试会漏掉泄露。请确认你的代理和泄露测试支持哪些地址族,并与你预期的行为对照。
预取和其他需要单独关注的查询
浏览器会在被要求加载之前就解析域名。包含大量链接的页面,可能在后台触发对这些链接目标的查询;地址栏也可能在用户还在输入时就解析主机名。这些推测性查询,包括 prefetch 和 dns-prefetch,走的是浏览器的解析路径,而不是你有意打开的那条连接的路径。如果这条路径离开了代理路由,泄露的域名就会包括从未访问过的站点。
这就是为什么一个正确处理了所有显式连接的代理,仍然可能在泄露测试中出现本地解析器。页面级请求、预取提示和后台查询都需要分别检查,不能用一次页面加载来代替。WebRTC 涉及的是与之相关但独立的问题,针对的是地址而不是域名,可参阅 WebRTC 泄露防护文章。
你可以不依赖公共测试页面来检验推测性查询。使用一个你控制的域名,其权威域名服务器会记录查询;发布一个链接到该域名下唯一主机名的页面,通过你配置好的路由打开该页面,然后查看服务器的查询日志中出现了哪些解析器地址。如果有查询来自既不属于你的服务商、也不属于你选定解析器的地址,那就是单页泄露测试发现不了的泄露。
缓存会让情况变复杂。同一会话中早先解析过的域名,之后可能直接从内存应答,所以第二次访问得到干净的结果说明不了什么。请重新启动浏览器,使用从未解析过的主机名,并在每次配置变更后重复检查。
故障表现也各不相同。当配置的解析器不可达时,有些方案会悄悄切换到另一个解析器,而这种切换本身就是一条新路径,下文的测试必须能发现它。请优先选择会明显失败的配置,而不是悄悄把查询路由到别处的配置,并在你自己拥有的测试环境中屏蔽该解析器,观察浏览器的行为,以此确认你的配置属于哪一种。
在 SOCKS5H、本地 DNS、系统 DNS、DoH 和 VPN DNS 之间选择
没有任何单一控制能封闭所有缺口,所以有用的比较方式是看每种方法留下的剩余缺口。四个问题可以帮你给这些选项分类:谁来解析域名、在哪个地区、谁能记录日志,以及该设置只作用于这个浏览器还是整台机器。
SOCKS5H。 它让经由代理连接传递的域名远离本地解析器,除了方案本身之外不需要任何浏览器功能。它的缺口是服务商自己的上游解析器(你无法检查)、不支持远程解析的代理,以及浏览器在代理连接之外发出的查询。
使用 --bot-local-dns 的浏览器本地 DNS。 BotBrowser 内置的解析器让解析独立于服务商的 DNS 行为,在服务商拦截、改写或拖慢查询时很有帮助。缺口在于它只管浏览器一侧。对于由服务商负责解析的连接,它不会改变服务商的做法;而如果你把它指向某个具体的 DNS 服务器,那台服务器就成了你必须信任的新观察者。
系统 DNS。 把操作系统设置为选定的解析器,只是用另一个解析器取代了网络服务商的解析器。观察者并没有消失,而且新的解析器通常位于与代理无关的地区。这个设置也无法说明某个域名是由浏览器还是由代理解析的。
DNS over HTTPS。 RFC 8484 通过 HTTPS 传输 DNS 消息,使网络路径上的观察者无法读取或修改它们。解析器仍然能看到每个域名,它所在的地区仍然需要与路由一致,而且这个设置并不决定由谁来执行查询。DNS over HTTPS 文章介绍了浏览器控制项和解析器的局限。
VPN DNS。 VPN 通常让操作系统的 DNS 经过它的隧道,一次覆盖所有应用。这对按浏览器或按上下文路由来说过于粗放,VPN 运营方的解析器能看到这些域名,而且解析器所在的地区跟随 VPN 出口,而不是另一个独立的代理出口。
当缺口互不重叠时,组合使用会有帮助,例如 SOCKS5H 加上你有意选择的浏览器侧解析器。但组合也会增加决策,每多一个环节就多一个观察者,或者多一个出现不一致的地方。请选择观察者都能说清楚的最小组合,然后测试。
选择并不明显时,下面这条经验法则会有帮助。如果服务商支持 SOCKS5H 并且你信任它的解析器,就从它开始并加以验证。如果服务商拦截、改写或拖慢查询,或者只提供 SOCKS5,浏览器侧解析器能给你一套不依赖服务商 DNS 的策略。如果机器上的其他应用也需要保护,那是操作系统或 VPN 的工作,而不是某个浏览器参数能解决的。
运行泄露测试并解读结果
泄露测试只有在每次都以同样方式运行、并对照固定预期来解读时才有用。一个可重复的流程如下:
- 记录代理出口地址、所在国家,以及服务商声明的运营方。
- 使用你打算实际运行的完整参数,重新启动浏览器。
- 打开 dnsleaktest.com 之类的泄露测试,运行它的扩展测试,它会进行多次查询而不是一次。
- 记下测试报告的每个解析器,以及它的运营方和地区。
- 把这份列表与第 1 步对照。每个解析器都应该属于一个你能解释的运营方和地区。
任何差异都应视为检查失败。由你的网络服务商运营的解析器、位于与出口不符的国家的解析器,或者你没有选择过的运营方,都说明路由与查询并不一致。不要靠猜测把差异解释过去;只改变一个变量,例如代理方案或本地 DNS 参数,然后重新测试。请比较运营方和地区,而不是具体地址,因为大型运营方会在同一地区用许多地址应答。
结果中的模式能指向原因。出现你自己服务商的解析器,通常意味着某个域名在本地被解析,因此要检查方案以及预取是否被覆盖。地区不对的知名公共解析器,指向操作系统 DNS、DoH 设置或 VPN。地区正确但运营方出乎意料的解析器,指向服务商的上游选择,而你只能通过更换服务商来改变它。
泄露测试有其局限。它显示的是测试自己的服务器在那个时刻针对测试所用主机名看到的解析器。它观察不到对其他域名的查询,也看不到服务商一侧的日志,更不能证明匿名。一次干净的结果,只是证明某条路径在某一天是一致的。
第二个局限是范围。域名解析只是若干网络路径之一。通过 WebRTC 暴露的地址、代理自身的出口地址,以及连接的传输行为都是各自独立的检查,DNS 测试通过并不能说明它们。
下面的参数是用 SOCKS5H 代理和本地解析器运行 BotBrowser 的文档化方式:
chrome --bot-profile="path/to/profile.enc" \
--proxy-server="socks5h://user:[email protected]:1080" \
--bot-local-dns
在 Playwright 中使用同样的配置,可执行文件和配置文件的路径来自环境变量:
import { chromium } from 'playwright-core';
const browser = await chromium.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
args: [
`--bot-profile=${process.env.BOT_PROFILE_PATH}`,
'--proxy-server=socks5h://user:[email protected]:1080',
'--bot-local-dns',
],
});
const page = await browser.newPage();
await page.goto('https://www.dnsleaktest.com');
await browser.close();
当不同的浏览器上下文使用不同的代理时,预期是按上下文分别设定的。每个上下文都有自己的出口、自己的地区,因此也有自己预期的解析器运营方,所以应按每条路由各运行一次流程,并把结果并排保存。某一条路由的一次干净结果,不能说明另一条使用其他服务商的路由。
请事先决定检查失败后怎么办。有边界的处理方式效果最好:停止运行,保留配置和观察到的解析器列表,并且每次只改变一个变量。在查询仍无法解释时,不要用重试循环继续发送流量,也不要收集超出运营方和地区所需的信息。
只要代理服务商、代理方案、参数或浏览器版本发生变化,就重复检查,并让记录只包含运营方和地区。浏览历史不应出现在测试日志里。关于如何让地址、DNS 和 WebRTC 信号保持一致这一更大的问题,请参阅代理、DNS 与 WebRTC 一致性。
BotBrowser 支持什么,哪些不在它的范围内
--bot-local-dns 参数在文档中被标注为 ENT Tier1 功能。单独写该参数或使用 true,会通过浏览器内置解析器解析;false 会关闭它,由代理解析域名;IP 或 IP:port 值会让查询只发往该 DNS 服务器,端口默认为 53。无效的值按 false 处理。当自定义服务器没有返回答案时,BotBrowser 不会回退到系统解析器。对于受支持的代理连接,文档还说明双栈目标的选择会与当前代理路由保持一致,不需要额外参数。
BotBrowser 的文档说明 --bot-local-dns(ENT Tier1)是一个内置 DNS 解析器,能让域名解析独立于代理服务商的 DNS 行为,并且会把 DNS 预取查询通过同一条代理路径发送,因此团队可以运行可重复的 DNS 策略,并用泄露测试加以验证。BotBrowser 不能控制或保证代理服务商的上游 DNS、浏览器之外的 DNS 服务器或日志,也不能决定泄露测试结果该如何解读,它不能替代对 SOCKS5H 或可信服务商的选择。
实际上这意味着两件互相独立的事。浏览器一侧可以配置并且可重复,而服务商一侧是一个关于信任谁的决定,没有任何参数能替你做出。请把选用的方案、本地 DNS 设置、预期的解析器运营方和预期地区写在每份配置旁边,这样日后的变更就会表现为与已记录预期之间的差异。在修改生产配置之前,请查阅 DNS 泄露防护文档,确认当前的取值语义。
结论
选择一条你能说清运营方和地区的最小解析路径。用全新会话和未解析过的主机名验证它,同时覆盖推测性查询以及两种地址族;如果观测到的解析器无法由代理策略解释,就停止发布。
来源
本文属于指南: 代理配置总览:浏览器网络决策图