苹果在 WWDC 2018 发布 macOS Mojave 的时候,介绍了 Safari 如今具有了防护 fingerprinting 技术的能力。这个技术和指纹有什么关系,是用来作什么的,又有多值得普通用户担忧呢?让咱们从它的前因后果提及吧 :-)javascript
Fingerprinting 的本意是指纹采集,那么它在 Web 浏览器的语境下指代的是什么呢?来看看它所要解决的问题吧。html
在人类社会里,要想惟一标识一我的,姓名和身份证号足够吗?通常状况下,使用这些基于社会制度的约定并无问题,但不少时候这是不够的:前端
在 Web 中,若是把浏览器类比为人,那么咱们就有了很是对应的类比:User Agent 至关于姓名,而 cookie 就比如身份证。好比,Chrome 浏览器的 User Agent 里会用形如 Chrome/66.0.3359.181
的字段标明本身的名称和版本,而对于重名(不少用户使用同个版本的 Chrome)的状况,咱们还能够经过 cookie 来惟一标识用户。是否是很直观呢?但上面的三个问题在 Web 里咱们照样逃不掉:java
这就暴露出了这样假设「每一个人都是好人」的约定,其固有的脆弱性。故而咱们须要发展技术,来在生物学上惟一标识一我的,以及,在技术层面上惟一标识一个浏览器。对于前者,咱们有指纹、虹膜、DNA 等识别技术可供使用。相似地,对于后者,咱们所用到的技术就是下面所要介绍的 fingerprinting 了。git
某种程度上,fingerprinting 属于特别的奇技淫巧——彻底不按照一个东西本来的用途来使用它,而是开发出了新用途:程序员
在程序员的世界,这样的奇技淫巧就更多了。要想惟一标识出一个运行在某个 OS 平台上的浏览器,你能想到多少种方式呢?在这个方面,只要看看开源的 fingerprintjs2 库,你就能感觉到程序员们为了追踪用户能想出多么骚的操做。这些操做所涉及的维度主要包括但不限于:github
下面咱们逐一对这些维度作一些简要的介绍。web
最简单的 IP 地址收集并不须要客户端的配合,而主要是服务端的工做。好比,Web 站点服务端能够记录请求的 IP 地址,并据此得到用户的地理位置。若是用户添加了代理服务器,咱们能够经过检测 HTTP 头中的 X-Forwarded-For
字段来发现这种情形。在 HTTP 应用层和 IP 网络层之间,咱们也不难经过在服务端收集 TCP 包头的方式,获取一些传输层的信息。编程
获取上面这些信息,都只须要后端服务就足够了。那么这类数据的收集,是否就没有前端施展的空间了呢?并非这样的,让咱们看看两种特殊的 fingerprinting 方式:DNS Leak 和 WebRTC Leak。canvas
只须要在前端作一点微小的工做,咱们就可以定位用户所用的 DNS 服务器。具体地说,当你访问 example.com
的时候,只要在前端页面中随机生成一系列地址为形如 abcdefg.example.com
的图片,就可让浏览器发起对这些子域名的 DNS 查询。只要 example.com
控制了最后形如 ns1.example.com
的次级域名服务器,那么查询这些地址时逐级发起的 DNS 查询就可以被服务端记录下来,进而得到用户的 DNS 服务器。这样一来,若是仅仅对 HTTP 请求配置了代理,用户所用的 DNS 地址就可能泄露。这时若是用户使用了运营商默认就近分配的 DNS 服务器,那么就可能对服务端暴露出其真实所在的位置。
相比上面只须要插入动态连接的方式,WebRTC 泄露所须要前端的参与就更多了一点。咱们知道 WebRTC 能够用于支持视频推流一类的实时应用,而 Firefox 和 Chrome 对 WebRTC 的实现中,须要 STUN 协议来用于让两个处于 NAT 后的主机之间建立 UDP 通讯。而 STUN 服务器能够向用户返回本地和公网 IP。这样一来,咱们就能够用这种方式,在 JavaScript 中获取到用户 NAT 后所在内网的 IP 地址了。
若是想要体验上面所介绍的这几种 fingerprinting 方式所能收集到的数据,请戳这里。
上面的描述看起来主要是网络层面上的工做,但其实在浏览里的 JavaScript 范畴内,一样有大量的信息可供采集。
要想编程控制 Web 页面的 UI 与行为,咱们必须使用 JavaScript 来操做 DOM。而稍有经验的前端同窗们都知道,DOM 是挂载了很是多属性而很是沉重的。这也就意味着,DOM 中存储了大量关于浏览器的敏感信息:User-Agent、系统架构、系统语言、本地时间、时区、屏幕分辨率……而对于 HTML5 中新加入的形如电量、加速度计、信息、Timing 等特性的 API,不要说检测它们的具体值是多少,光是检测这些 API 的存在性,信息量就很是大了。而对这些属性的检测难度有多低呢?咱们只须要在 JavaScript 中访问 navigator.xxx
属性,就能够轻易地得到一个浏览器的「身高、体重、血型、星座……」了。
固然了,现代浏览器为了不一些敏感的 DOM 属性泄露,会使用一些安全策略来限制一些属性的访问。但对于 fingerprinting 的场景来讲,有些安全策略和掩耳盗铃差很少。让咱们看看 fingerprintjs2 中的一段源码:
// https://bugzilla.mozilla.org/show_bug.cgi?id=781447
hasLocalStorage: function () {
try {
return !!window.localStorage
} catch (e) {
return true // SecurityError when referencing it means it exists
}
},
复制代码
这个套路在整个库中出现的次数还真很多。藏着掖着不让我访问?这不是欲盖弥彰嘛 :-)
对 JavaScript 的 fingerprinting demo,请移步这里。
Flash 和 Java 会在不一样程度上泄露用户设备的信息。
在浏览器的层面,它们对应的 navigator.plugins
字段自己就是一个大坑:列举出全部用户安装的插件及其详细的版本号信息,这自己就大大增长了浏览器的惟一性。例以下面的代码,在老版本的 Firefox 中就能轻易地获取用户浏览器的插件信息:
for (plugin of navigator.plugins) { console.log(plugin.name); }
"Shockwave Flash"
"QuickTime Plug-in 7.7.3"
"Default Browser Helper"
"Unity Player"
"Google Earth Plug-in"
"Silverlight Plug-In"
"Java Applet Plug-in"
"Adobe Acrobat NPAPI Plug-in, Version 11.0.02"
"WacomTabletPlugin"
复制代码
亡羊补牢地,浏览器厂商增长了对这个属性的 "cloaking" 保护,屏蔽了常见插件之外的插件名称。在如今的 Firefox 里,上面的代码结果应当是这样的:
for (plugin of navigator.plugins) { console.log(plugin.name); }
"Shockwave Flash"
"QuickTime Plug-in 7.7.3"
"Java Applet Plug-in"
复制代码
可是,这个能力并不能阻止追踪者经过形如 navigator.plugins["Shockwave Flash"]
的方式来主动探测插件的安装。所以,这是浏览器插件 API 的第一个信息泄露隐患。
在浏览器层面以外的插件 Runtime 层面,Flash 和 Java Applet 又存在着什么可能被 fingerprinting 的地方呢?
Flash 能够提供 AS3 语言读取系统信息的能力:除了 Flash 版本外,这还包括 OS 版本、硬件厂商、Web 浏览器架构、分辨率等信息,以及许多用于描述硬件和系统多媒体兼容性的属性。至于 Java Applet,它除了能够提供 JVM 的描述、系统版本、用户 locale 信息以外,甚至还有部分文件系统、内存占用、网络状态等信息。这些信息结合在一块儿,无疑会大大下降追踪的难度。
在这些安全问题的阴影下,Flash 和 Java Applet 都已经淡出现代 Web 了。而上面的 navigatior.plugins
API,也已经被废弃了。
到目前为止介绍的几种 fingerprinting 方式,所得到的数据多半没有特别高的惟一性(如 UA),或者可能存在较多的抖动(如 IP 地址)。接下来,咱们会提到一些真正和「指纹」相近的特性,它们更接近 fingerprinting 技术的精华。
这里是 Flash fingerprinting 的示例。还好,你的浏览器可能已经不支持 Flash 了 :-)
看似平凡的字体,其实能引出一个很是庞大的话题。在 fingerprinting 技术中,字体的角色不可或缺。
在我司 @小米 老板的分享里提到了,字体排版的计算牵扯到很是多的参数:baseline / ligatures / kerning……它的复杂程度很高,以致于浏览器须要依赖操做系统的绘图库(如 Linux 上的 Pango、macOS 上的 CoreText 和 Windows 上的 DirectWrite)。不只是这些库的行为会有本身微妙的区别,浏览器还会经过 CSS 属性继续控制字体渲染的过程。这样一来,咱们就能够经过字体的排版结果,获知计算过程了。这个流程看似精妙,但其实很是简单:
<span>
标签。只要这样简单的步骤,咱们就能获知两个关键的信息:
要查看基于字体排版所计算出的 fingerprint 差别,请参见这里。
HTML 中的 Canvas API 为 JavaScript 提供了对渲染内容的像素级控制。咱们知道,在 Canvas 中除了对基本的形状、文本、绘制模式的支持外,还可以将 Canvas 内容导出为图片(若是你使用过各类朋友圈连接里的「保存到相册」功能,你就用过这个 API)。在图片格式的层面,浏览器使用不一样的图片处理引擎、导出参数、压缩级别,这使得最终图片即使每一个像素都彻底一致,导出文件的哈希值很容易存在细微的区别。而在操做系统的层面,不一样的字体渲染方式、抗锯齿配置、子像素渲染方式也会带来微妙的区别。综合下来,咱们就可以用 Canvas 获得「指纹」了。
在 fingerprintjs2 里,这个特性的源码实现很是简洁:
getCanvasFp: function () {
var result = []
var canvas = document.createElement('canvas')
// ...
// 调用了一大堆 canvas API 以后
if (canvas.toDataURL) { result.push('canvas fp:' + canvas.toDataURL()) }
},
复制代码
你可能找不到其它「核心实现」里连一个 if-else 都不带的代码段了……但这个手段的效果很是的好。在这个示例页面中,你能够查看本身浏览器的 Canvas 指纹:
Your Fingerprint
Signature ✔ 4FAFB231
Uniqueness 99.56% (1130 of 258561 user agents have the same signature)
复制代码
这个手段很容易得到很是高的 Uniqueness。
WebGL 是个比 Canvas 更加底层的 API,你能够用它得到 3D 绘图的强大能力。基于 WebGL 的 fingerprinting,原理与字体、Canvas 并无什么区别,不外乎如下两点:
戳这里是相应的 demo 页面。多是 Demo 没有引入字体绘制的缘由,这里得到的图片惟一性并不过高,个人 Safari 和 Chrome 竟然可以得到彻底一致的图片哈希值……
上面介绍的一堆手段结合起来,就能得到很是强大的工业级 fingerprinting 库了。若是你对实际效果有疑问,不妨访问 fingerprintjs2 项目主页,尝试这样的操做:
不出意外地,在同一个 Chrome 中修改各类常见的配置,fingerprint 是不会改变的。而不论更换成另外一台电脑上的相同版本浏览器或是同一台电脑上的不一样浏览器,都会带来不一样的 fingerprint 结果。这就是 fingerprinting 技术的强大之处了。
根据 Mozilla 的数据1,在对站点 100 万次的访问里,有 83.6% 的浏览器有着惟一的 fingerprint,对于启用了 Flash 或 Java 的浏览器,这一数据达到了 94.2%。
另外一个有意思的数据是,常常被写进隐私政策的 cookie,对 fingerprint 惟一性的贡献很是微弱。数据显示,在上面所介绍的追踪手段中,浏览器插件可以带来 15.4 个比特位的熵增,而启用 cookie 只能带来 0.353 个比特位的熵增。这但是 2^15
和 2^0.3
的数量级区别啊——而且统计数据尚未计入效果更好的 Canvas 追踪技术。如今能够理解各类垃圾网站上的小广告为了找到你,有多么努力了吧 :-)
外国用火药制造子弹御敌,中国却用它作爆竹敬神;外国用罗盘针航海,中国却用它看风水;外国用鸦片医病,中国却拿来当饭吃。
目前,各大浏览器厂商都在努力提供更好的隐私保护策略。在本文开头说起的 Safari,就会使用简化的配置项来加大追踪的难度。但 Fingerprinting 技术的背后,值得咱们思考的是隐私的价值对技术的滥用。一方面,你会为了更好的隐私保护,而禁用 Canvas、WebGL 和字体渲染吗?恐怕多数人都很难回得去了吧。而另外一方面,就像 Google 会用 AI 下围棋,而百度会拿来优化假药广告投放同样,技术自己并无对错,重要的是使用它的人。