就算禁用 Cookie,甚至网上冲浪时只用「隐身窗口」,你的现代浏览器还是泄露了太多信息,使得网站能够追踪到你。

一、信息熵与指纹

信息熵(entropy)是指消息中含有的信息量。在网上冲浪的过程中,浏览器向网站服务器发出请求,网站服务器根据请求内容响应。在浏览器与网站服务器的交互中,浏览器会向网站暴露许多不同消息,比如最简单的 User-Agent 里就包含了浏览器型号、版本、操作系统等信息。这些消息单独一条的熵并不高,但是所有的消息组合起来,其熵就相对高了;当浏览器向网站暴露的熵足够高时,网站就能利用这一信息来追踪、定位用户。

假想这么一个例子:阳光中学每个年级 10 个班,每个班的人数相同。你知道刘涛是三年二班的学生,那这条消息的的熵便是 4.91 bit 左右(log(30, 2));如果你又了解到刘涛出生于五月份,那这条消息的熵约为 3.58 bit 左右(log(12, 2));假设你还知道这个中性名字的主人是个女生而不是男生,那你又获得了一条 1 bit 熵的消息。每条消息的熵都不高,但是三条消息加起来的熵便是 9.49 bit 了,或者说,同时具有这三个特征的学生,在阳光中学里平均每 720 个人才有一个。

正如人的指纹可以用来识别不同的人一样,当浏览器暴露的信息的熵足够高时,这些信息也可以用来识别浏览器(用户),这被称为「浏览器指纹」。随着 Web 技术的发展,现代浏览器向网站暴露的信息也越来越多,浏览器指纹也越来越复杂,复杂到让你在十万、甚至百万用户之中是独一无二的,因此网站可以利用这一特性识别、追踪你。

比如你在某论坛发了一个帖子,发完之后你清空浏览器 Cookie 和缓存,重拨宽带,并且你又开了一个异国的 VPN 再次访问这个论坛,换个马甲回复了刚才的帖子。即使你这么谨慎了,网络管理员还是有可能知道回帖的用户和发帖的用户其实是同一个人。

二、现代浏览器的指纹

浏览器指纹不是什么新鲜概念,EFF 早在 2010 年就启动了一个有关浏览器指纹的研究。当时给我留下深刻印象的是 Adobe Flash,通过 Flash 获取系统字体列表,可以增加相当高的信息熵。如今多年过去,Adobe Flash 早已作土,但是更多的「现代」浏览器技术发展了起来。

HTTP Header

这是最基本的信息。使用开发者工具可以看到浏览器在请求一个网页时到底发送了哪些 header。httpbin.org 可以返回它收到的 header 内容。比如我的:

{
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-US,en;q=0.9",
    "Connection": "close",
    "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
  }
}

可以看到,Accept-Language 里发送了我的浏览器语言列表。虽然很少有网站会读取并使用这个 header(比如 Google 就是无视这个 header 的,而是根据 IP 地址判断国家,进而决定网页的语言)。这个语言列表并不是简单的 ISO 369 列表,而是会加上各种变体及权重组合,因此可能性还是比较多的。

User-Agent 里则包含了浏览器信息和操作系统信息。我使用的是 Arch Linux 编译的 Chromium 浏览器,这儿只显示了 X11; Linux x86_64,并未体现具体的发行版,但是有些发行版,如 Ubuntu,则会把自己的发行版信息编译进 UA 里。Windows 和 macOS 上的预编译版本 Chrome 也会包含更详细的操作系统信息。

Flash, ActiveX, Silverlight 与 Java Applet

前两者基本已经入土,后两者使用率并不高,但是如果你的浏览器还在使用它们的话,它们能让网站获取很多高熵的信息。比如 Adobe Flash 能获取到系统安装所有的字体的列表,这就是一条高熵信息:安装 MS Office 和 Adobe 套件时,都会往系统里安装字体;为了正确显示一些外来的文件,用户也可能主动安装字体。两台计算机拥有完全一模一样的字体列表的几率并不高。

至于 ActiveX 和 Java Applet 之类,能收集的信息就更多了。好在它们从未像 Flash 那样被默认允许过。

JavaScript

即使用户伪装了 HTTP Header 里的 User-Agent 字符串,网站也可以通过 JavaScript 获取到 navigator.userAgent。此外,JavaScript 还可以拿到时区、屏幕大小、电脑类型(笔记本/台式机)、电池余量等信息。注意,通过 Intl.DateTimeFormat().resolvedOptions().timeZone 是可以拿到具体的 IANA 时区名字的:比如一位住在加拿大的用户和一位住在美国的用户,虽然他们都过着东部时间(UTC-4 或 UTC-5),但是这个函数会根据系统区域设置返回 America/TorontoAmerica/New_York。其他时区偏移量相同但是时区名字不同的地区(如 Asia/Shanghai 和 Asia/Hong_Kong)也是同理。因此时区信息的熵其实挺高的。

JavaScript 也可以用来做字体检测,只不过精度没有 Flash 那么高。通过测量字符的高度和字符之间的距离,可以推测出系统中安装了哪些字体,不过这个推测只能精确到 metric-compatible 的字体集。比如 Windows 系统中的三大字体 Arial, Times New Roman, Courier New 分别就有对应的 metric-compatible 的开源版本 Arimo, Tinos, Cousine

WebGL 与 Canvas

这两个都是 JavaScript 的绘图 API。WebGL 可以用来获取到渲染设备型号(i.e. 你的显卡型号)。Canvas 则比较有意思,由于不同系统的字体渲染引擎不同,对抗锯齿、次像素渲染等算法也不同,使用 canvas 将同样的文字转成图片,得到的结果也是不同的。BrowserLeaks 展示了 35 个不同用户将同样的内容转成图片得到的结果差异。这些差异是一条熵很高的信息。在我做测试的时候,网站显示在它的 238238 个测试结果里,只有 5 个用户与我一致。

WebRTC

WebRTC 主要用于在网页上进行视频会议。由于涉及到点对点连接,支持 WebRTC 的浏览器会暴露用户的本地局域网 IP 地址给网站。Firefox 可以将 media.peerconnection.enabled 设置为 false 来禁用这一行为,而 Chrome 则需要安装 WebRTC Network Limiter 插件来阻止这一功能。

CSS

就算用户禁用了 JavaScript(此时许多流行的网站已经无法工作了),网站也可以通过纯 CSS 来获取到一些信息,比如这样:

@media(device-width: 1920px) {
  body {
    background: url("https://example.org/1920.png");
  }
}

通过统计 1920.png 这个图片的请求日志,便可知道有哪些用户的窗口宽度是 1920px。

在 Mozilla Firefox 中,甚至曾经存在过可以直接查询 Windows 系统版本和 Windows 主题的 CSS 查询。这个特性在数月前被修正为只能用于内部页面了。

DNS 泄露

有些网站(如 Netflix)只允许特定国家的用户访问特定的内容,常见的规避方法是用 VPN 改变自己出口 IP 地址:用户连接 VPN 之后访问网站,网站看到的连接 IP 地址是 VPN 服务器的出口 IP 地址,而不是用户的真实 IP 地址。但如果 DNS 配置不当,这个网站可以通过获取用户的 DNS 信息方式,推测出用户的(大致)地理位置,而这种技术甚至都不需要任何 JavaScript,只需要最平实的 HTML。

假设有一位位于加拿大的用户想观看美国 Netflix,他连接了一个美国 VPN,却依然在使用加拿大当地 ISP DNS,而 Netflix 在网页上引用了几张图片:

https://random-111.netflix.com/foo.png
https://random-222.netflix.com/foo.png
https://random-333.netflix.com/foo.png

当浏览器加载这个网页时,会去解析这些 *.netflix.com 的域名,由于这些随机生成的域名在缓存 DNS 中并没有,这些请求最终会被用户的 DNS 递归到 netflix.com 的权威名字服务器上,而在这一过程中,netflix.com 的权威名字服务器也就知道了用户的 DNS 向上递归时最后一跳的 IP 地址。于是 Netflix 便能发现这个用户的美国 IP 地址是 VPN 笼罩下的假象,他其实居住在加拿大。

在中国大陆网民中十分流行的翻墙伴侣 ChinaDNS 也受到 DNS 泄露的影响,它的使用方法是配置两个上游 DNS,一个位于中国大陆,一个位于非中国大陆,每当其收到一个 DNS 请求时,它同时将请求同时发给 A, B 两个服务器,再将返回的结果与 chnroutes 对比,判断出哪个是真正有效的解析并丢弃被污染的结果。如果用户将中国大陆的上游 DNS 设置为 ISP DNS,则使用上述方法的网站便可知道用户精确到省级的地理位置,即使用户已经翻墙。

惹不起,也躲不起?

以上提到的几项技术,大多都可以通过一些方法禁用。但其实「禁用」这一行为本身,也是一种信息。一些网页在打开时会弹一个请求,获取通知、地理位置、录音等各种权限,如果用户觉得厌烦而点了拒绝,那浏览器便会记住这一选择,当用户下一次打开网站的时候,网站便能发现这一用户曾经拒绝过权限请求,从而将拒绝的用户与其他允许的,或是未做选择的用户区别开来。

如果用户装了广告拦截或是 uBlock Origin 等内容屏蔽扩展,网站也可以故意在网页上插入一些注定会被屏蔽的内容,来判断你装了哪些屏蔽扩展,将装扩展的用户与其他用户区别开来。

甚至,当用户打开了浏览器的 Do Not Track 功能时,网站也可以违背道德,将 DNT=1 的用户与 DNT=0 或不发送 DNT 的用户区别开来。

三、隐藏浏览器指纹?

不行。浏览器指纹是浏览器的固有属性,哪怕你的浏览器不发送 UA、禁用各种现代浏览器技术、禁用 JavaScript,「没有指纹」反而成了最可疑的指纹,因为在成千上万的用户里,你的浏览器是唯一没有指纹的,自然就鹤立鸡群了。

但是你可以大隐隐于市,让你的浏览器指纹变得大众化。比如现在市面上最流行的操作系统和浏览器组合是 Windows 10 + Google Chrome,那你就使用这一组合。如果你想连屏幕大小、时区等信息一并模拟,甚至可以装个虚拟机来上网(目前并没有有效可靠的从网页上检测浏览器是否运行在虚拟机里的技术,如果有的话,那运行在虚拟机里反而也会成为鹤立鸡群的特征)。

对于一般用户来说,使用最流行的操作系统和浏览器组合进行上网冲浪已经足够「大隐隐于市」了,但是对于一些注重网络隐私的人来说,他们还需要更多的措施。这方面我比较佩服两个人:Satoshi编程随想。他们在网络上活跃多年,但至今没有人成功地将这两个身份与现实世界中的身份或是网络上其他的身份联系到一起。

本文地址:https://wzyboy.im/post/1130.html


2020 年更新:TLS 指纹?

除了浏览器在 HTTP 层面所展现的指纹,浏览器在与 HTTPS 服务器进行 TLS 握手的时候,也会展现出指纹。TLS 指纹不仅影响浏览器,也影响其他使用 TLS 的应用,比如翻墙工具。对于在危险环境中的网民而言,TLS 指纹比浏览器指纹更具风险,因为审查者(国家暴力机关)可以仅凭加密后网络流量定位翻墙工具的用户和翻墙服务器,从而封锁翻墙服务器,并对翻墙用户进行物理清除。

最近一个流行的翻墙工具 V2Ray 爆出了多个安全漏洞(已修复),其中一个就是关于 TLS 指纹的。简单来说,V2Ray 客户端默认会使用一组硬编码的 CipherSuites,本意是为了排除不够安全的算法,但是这组东西的成员及其排列顺序太独特,以至于 V2Ray 的 ClientHello 和别的 TLS 客户端都长得不一样,反而成为了十分扎眼的特征。网络管理员(以及审查者)甚至可以通过简单的明文匹配过滤掉 V2Ray 的 TLS 流量,而不影响其他 TLS 应用(如浏览器、使用默认 TLS 参数的 Golang 程序等)。

为了研究各 TLS 客户端(主要是浏览器)ClientHello 的差异,科罗拉多大学搞了个网站用于收集和统计 TLS 的指纹:tlsfingerprint.io。他们还做了一个叫 uTLS 的 Golang 库,用来模拟流行浏览器的 TLS 指纹,让基于 TLS 的翻墙工具「大隐隐于市」。在上述网站上甚至可以选择一个排名靠前的 TLS 指纹,一键生成 uTLS 代码,让你的 Golang 程序轻松拥有爆款 TLS 指纹。

然而模拟指纹有个问题:万一模仿的不像,那这一点「不像」就是最显眼的特征。Tor 曾经模拟 Firefox ESR 的指纹,但是由于该版本 Firefox 市场占有率太低,反而可以让审查者轻易判断出哪些用户在使用 Tor。

于是有人想出了一个天才的想法:直接使用浏览器网络栈。NaïveProxy 就是这样一个项目,它直接把 Chromium 的网络代码拿来用了,因此至少从 TLS 握手的行为来说,它和 Chromium 是一模一样的。

Shadowsocks 和 V2Ray (VMess) 诞生于 TLS 还不是那么容易部署的年代,使用了特立独行的加密/混淆方法,因此这几年来频繁被 GFW 识别并清除。随着 Let's Encrypt 和 TLS 1.3(加密服务器证书)的普及,各种翻墙工具直接拿现成的 TLS 做传输,大隐隐于市,然后专心做鉴权和客户端整合才是有前途的发展方向。


欢迎留下评论。评论前,请先阅读《隐私声明》。