WWDC 中提到的瀏覽器 Fingerprinting 有多可怕?

doodlewind發表於2018-06-06

蘋果在 WWDC 2018 釋出 macOS Mojave 的時候,介紹了 Safari 現在具備了防禦 fingerprinting 技術的能力。這個技術和指紋有什麼關係,是用來做什麼的,又有多值得普通使用者擔心呢?讓我們從它的來龍去脈說起吧 ?

何謂 Fingerprinting

Fingerprinting 的本意是指紋採集,那麼它在 Web 瀏覽器的語境下指代的是什麼呢?來看看它所要解決的問題吧。

在人類社會裡,要想唯一標識一個人,姓名和身份證號足夠嗎?一般情況下,使用這些基於社會制度的約定並沒有問題,但很多時候這是不夠的:

  • 姓名可以隨意更換,還有大量重名。
  • 身份證可能被偽造或冒用。
  • 極端情況(如一具無名屍體)既沒有姓名也沒有身份證。

在 Web 中,如果把瀏覽器類比為人,那麼我們就有了非常對應的類比:User Agent 相當於姓名,而 cookie 就好比身份證。比如,Chrome 瀏覽器的 User Agent 裡會用形如 Chrome/66.0.3359.181 的欄位標明自己的名稱和版本,而對於重名(很多使用者使用同個版本的 Chrome)的情況,我們還可以通過 cookie 來唯一標識使用者。是不是很直觀呢?但上面的三個問題在 Web 裡我們照樣逃不掉:

  • User Agent 就像姓名,在現代瀏覽器裡基本可以隨意更換。
  • Cookie 就像身份證。只要知道別人的身份證號(cookie 值),就可以把身份偽裝成別人。
  • 對於匿名或惡意的訪問,往往上面兩者獲得的資訊都是無效的。

這就暴露出了這樣假設「每個人都是好人」的約定,其固有的脆弱性。故而我們需要發展技術,來在生物學上唯一標識一個人,以及,在技術層面上唯一標識一個瀏覽器。對於前者,我們有指紋、虹膜、DNA 等識別技術可供使用。類似地,對於後者,我們所用到的技術就是下面所要介紹的 fingerprinting 了。

Web Fingerprinting 技術速覽

某種程度上,fingerprinting 屬於特別的奇技淫巧——完全不按照一個東西原本的用途來使用它,而是開發出了新用途:

  • 指紋原本是用來防滑的,我們拿來鑑定一個人。
  • 虹膜原本是用來調節瞳孔大小的,我們拿來鑑定一個人。
  • DNA 原本是用來送給妹子製造後代的,我們拿來鑑定一個人。

在程式設計師的世界,這樣的奇技淫巧就更多了。要想唯一標識出一個執行在某個 OS 平臺上的瀏覽器,你能想到多少種方式呢?在這個方面,只要看看開源的 fingerprintjs2 庫,你就能感受到程式設計師們為了追蹤使用者能想出多麼騷的操作。這些操作所涉及的維度主要包括但不限於:

  • IP 地址
  • JavaScript 行為
  • Flash 與 Java 外掛
  • 字型
  • Canvas
  • WebGL

下面我們逐一對這些維度做一些簡要的介紹。

IP 地址

最簡單的 IP 地址收集並不需要客戶端的配合,而主要是服務端的工作。比如,Web 站點服務端可以記錄請求的 IP 地址,並據此獲得使用者的地理位置。如果使用者新增了代理伺服器,我們可以通過檢測 HTTP 頭中的 X-Forwarded-For 欄位來發現這種情形。在 HTTP 應用層和 IP 網路層之間,我們也不難通過在服務端收集 TCP 包頭的方式,獲取一些傳輸層的資訊。

獲取上面這些資訊,都只需要後端服務就足夠了。那麼這類資料的收集,是否就沒有前端施展的空間了呢?並不是這樣的,讓我們看看兩種特殊的 fingerprinting 方式:DNS LeakWebRTC Leak

只需要在前端做一點微小的工作,我們就能夠定位使用者所用的 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 行為

上面的描述看起來主要是網路層面上的工作,但其實在瀏覽裡的 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=781447hasLocalStorage: function () { 
try {
return !!window.localStorage
} catch (e) {
return true // SecurityError when referencing it means it exists
}
},複製程式碼

這個套路在整個庫中出現的次數還真不少。藏著掖著不讓我訪問?這不是此地無銀三百兩嘛 ?

對 JavaScript 的 fingerprinting demo,請移步這裡

Flash 與 Java 外掛

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 屬性繼續控制字型渲染的過程。這樣一來,我們就可以通過字型的排版結果,獲知計算過程了。這個流程看似精妙,但其實非常簡單:

  1. 在使用者不可見的地方,用各種特殊字型渲染 <
    span>
    標籤。
  2. 測量所獲得的標籤 Bounding Box。

只要這樣簡單的步驟,我們就能獲知兩個關鍵的資訊:

  • 使用者是否安裝了某個字型(未安裝的字型會 Fallback 到預設字型)。
  • 字型渲染方式不同導致的畫素級 Bounding Box 排版差異。

要檢視基於字型排版所計算出的 fingerprint 差異,請參見這裡

Canvas

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 FingerprintSignature ✔ 4FAFB231Uniqueness 99.56% (1130 of 258561 user agents have the same signature)複製程式碼

這個手段很容易獲得非常高的 Uniqueness。

WebGL

WebGL 是個比 Canvas 更加底層的 API,你可以用它獲得 3D 繪圖的強大能力。基於 WebGL 的 fingerprinting,原理與字型、Canvas 並沒有什麼區別,不外乎以下兩點:

  • 全面判斷瀏覽器對 WebGL API 的支援(是的,光 API 就有 88 個)。
  • 繪製特殊形狀,而後計算所渲染得到圖片的雜湊值。

戳這裡是相應的 demo 頁面。可能是 Demo 沒有引入字型繪製的原因,這裡獲得的圖片唯一性並不太高,我的 Safari 和 Chrome 居然能夠獲得完全一致的圖片雜湊值……

Real World 表現

上面介紹的一堆手段結合起來,就能獲得非常強大的工業級 fingerprinting 庫了。如果你對實際效果有疑問,不妨訪問 fingerprintjs2 專案主頁,嘗試這樣的操作:

  1. 先在你的 Chrome 普通模式下,生成一個 fingerprint。
  2. 在 Safari 下,也生成一個 fingerprint。
  3. 和同事的同款 Mac 對比一下結果是否有區別。
  4. 改掉你的 User Agent 後重新整理頁面,看看 fingerprint 有沒有區別。
  5. 進入 Chrome 的匿名模式,重新生成一個 fingerprint,看看是否一致。

不出意外地,在同一個 Chrome 中修改各種常見的配置,fingerprint 是不會改變的。而不論更換成另一臺電腦上的相同版本瀏覽器或是同一臺電腦上的不同瀏覽器,都會帶來不同的 fingerprint 結果。這就是 fingerprinting 技術的強大之處了。

根據 Mozilla 的資料1,在對站點 100 萬次的訪問裡,有 83.6% 的瀏覽器有著唯一的 fingerprint,對於啟用了 Flash 或 Java 的瀏覽器,這一資料達到了 94.2%。

另一個有意思的資料是,經常被寫進隱私政策的 cookie,對 fingerprint 唯一性的貢獻非常微弱。資料顯示,在上面所介紹的追蹤手段中,瀏覽器外掛能夠帶來 15.4 個位元位的熵增,而啟用 cookie 只能帶來 0.353 個位元位的熵增。這可是 2^152^0.3 的數量級區別啊——並且統計資料還沒有計入效果更好的 Canvas 追蹤技術。現在可以理解各種垃圾網站上的小廣告為了找到你,有多麼努力了吧 ?

總結

外國用火藥製造子彈禦敵,中國卻用它做爆竹敬神;外國用羅盤針航海,中國卻用它看風水;外國用鴉片醫病,中國卻拿來當飯吃。

目前,各大瀏覽器廠商都在努力提供更好的隱私保護策略。在本文開頭提及的 Safari,就會使用簡化的配置項來加大追蹤的難度。但 Fingerprinting 技術的背後,值得我們思考的是隱私的價值對技術的濫用。一方面,你會為了更好的隱私保護,而禁用 Canvas、WebGL 和字型渲染嗎?恐怕多數人都很難回得去了吧。而另一方面,就像 Google 會用 AI 下圍棋,而百度會拿來優化假藥廣告投放一樣,技術本身並沒有對錯,重要的是使用它的人。

參考

來源:https://juejin.im/post/5b17de31f265da6e397b70f4?from=timeline&isappinstalled=0

相關文章