蘋果在 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 下圍棋,而百度會拿來優化假藥廣告投放同樣,技術自己並無對錯,重要的是使用它的人。