├── .gitignore ├── static └── images │ ├── topics.png │ ├── PrefixLSH.png │ ├── amiunique.png │ ├── SortingLSH.png │ ├── audio-after.png │ ├── audio-before.png │ ├── battery-api.png │ ├── firefox-etp.png │ ├── font-hinting.png │ ├── font-width.png │ ├── webgl-report.png │ ├── IPA-match-key.png │ ├── Unified-ID-2.0.jpg │ ├── bounce-tracker.png │ ├── tracking-party.png │ ├── IPA-helper-node.png │ ├── link-decoration.png │ ├── oscillator-sample.png │ ├── picasso-algorithm.png │ ├── webgl-report-vm-2.png │ ├── webgl-report-vm.png │ ├── custom-protocol.png.png │ ├── mediadevice-labels.png │ ├── picasso-ios-compare.png │ ├── browser-extension-dom.png │ ├── cookie-in-http-header.png │ ├── emoji-chromium-firefox.png │ ├── mediadevice-no-labels.png │ ├── browser-extension-style.png │ ├── canvas-sub-pixel-rendering.png │ ├── cookie-cross-site-tracking.png │ ├── ublock-origin-filter-lists.png │ ├── browser-extension-dom-detection.png │ ├── browser-extension-http-requests.png │ ├── error-message-on-different-browsers.png │ ├── navigator-object-on-different-browsers.png │ └── string-representation-on-different-browsers.png ├── .gitmodules ├── content ├── docs │ ├── browser-fingerprinting │ │ ├── techniques │ │ │ ├── _index.md │ │ │ ├── battery-status-fingerprinting.md │ │ │ ├── scheme-flooding.md │ │ │ ├── audio-fingerprinting.md │ │ │ ├── ad-blocker-fingerprinting.md │ │ │ ├── webgl-fingerprinting.md │ │ │ ├── attributes.md │ │ │ ├── fonts-enumeration.md │ │ │ ├── cpu-fingerprinting.md │ │ │ ├── webrtc-and-media-device-fingerprinting.md │ │ │ ├── fingerprinting-via-css.md │ │ │ ├── canvas-fingerprinting.md │ │ │ └── browser-extensions-fingerprinting.md │ │ ├── _index.md │ │ ├── browser-fingerprinting-and-information-theory.md │ │ ├── defense.md │ │ └── how-defense-failed-and-privacy-paradox.md │ ├── stateful-tracking │ │ ├── _index.md │ │ ├── firefox-tracking-protection.md │ │ ├── content-blocking-circumvention.md │ │ ├── cross-site-tracking-without-cookie.md │ │ ├── content-blocking.md │ │ ├── cross-site-tracking-with-cookie.md │ │ └── apple-itp.md │ ├── _index.md │ ├── privacy-preserving-web-tracking │ │ ├── unified-id-2.0.md │ │ ├── topics.md │ │ ├── _index.md │ │ ├── pcm.md │ │ ├── ipa.md │ │ └── floc.md │ └── usages-and-categories.md └── _index.md ├── archetypes └── default.md ├── README.md ├── resources └── _gen │ └── assets │ └── scss │ ├── book.scss_50fc8c04e12a2f59027287995557ceff.json │ └── book.scss_50fc8c04e12a2f59027287995557ceff.content └── config.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .hugo_build.lock -------------------------------------------------------------------------------- /static/images/topics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/topics.png -------------------------------------------------------------------------------- /static/images/PrefixLSH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/PrefixLSH.png -------------------------------------------------------------------------------- /static/images/amiunique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/amiunique.png -------------------------------------------------------------------------------- /static/images/SortingLSH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/SortingLSH.png -------------------------------------------------------------------------------- /static/images/audio-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/audio-after.png -------------------------------------------------------------------------------- /static/images/audio-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/audio-before.png -------------------------------------------------------------------------------- /static/images/battery-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/battery-api.png -------------------------------------------------------------------------------- /static/images/firefox-etp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/firefox-etp.png -------------------------------------------------------------------------------- /static/images/font-hinting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/font-hinting.png -------------------------------------------------------------------------------- /static/images/font-width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/font-width.png -------------------------------------------------------------------------------- /static/images/webgl-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/webgl-report.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes/hugo-book"] 2 | path = themes/hugo-book 3 | url = https://github.com/alex-shpak/hugo-book 4 | -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 2 3 | title: "Browser Fingerprinting Techniques" 4 | --- -------------------------------------------------------------------------------- /static/images/IPA-match-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/IPA-match-key.png -------------------------------------------------------------------------------- /static/images/Unified-ID-2.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/Unified-ID-2.0.jpg -------------------------------------------------------------------------------- /static/images/bounce-tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/bounce-tracker.png -------------------------------------------------------------------------------- /static/images/tracking-party.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/tracking-party.png -------------------------------------------------------------------------------- /archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /static/images/IPA-helper-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/IPA-helper-node.png -------------------------------------------------------------------------------- /static/images/link-decoration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/link-decoration.png -------------------------------------------------------------------------------- /static/images/oscillator-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/oscillator-sample.png -------------------------------------------------------------------------------- /static/images/picasso-algorithm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/picasso-algorithm.png -------------------------------------------------------------------------------- /static/images/webgl-report-vm-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/webgl-report-vm-2.png -------------------------------------------------------------------------------- /static/images/webgl-report-vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/webgl-report-vm.png -------------------------------------------------------------------------------- /static/images/custom-protocol.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/custom-protocol.png.png -------------------------------------------------------------------------------- /static/images/mediadevice-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/mediadevice-labels.png -------------------------------------------------------------------------------- /static/images/picasso-ios-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/picasso-ios-compare.png -------------------------------------------------------------------------------- /static/images/browser-extension-dom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/browser-extension-dom.png -------------------------------------------------------------------------------- /static/images/cookie-in-http-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/cookie-in-http-header.png -------------------------------------------------------------------------------- /static/images/emoji-chromium-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/emoji-chromium-firefox.png -------------------------------------------------------------------------------- /static/images/mediadevice-no-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/mediadevice-no-labels.png -------------------------------------------------------------------------------- /static/images/browser-extension-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/browser-extension-style.png -------------------------------------------------------------------------------- /static/images/canvas-sub-pixel-rendering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/canvas-sub-pixel-rendering.png -------------------------------------------------------------------------------- /static/images/cookie-cross-site-tracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/cookie-cross-site-tracking.png -------------------------------------------------------------------------------- /static/images/ublock-origin-filter-lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/ublock-origin-filter-lists.png -------------------------------------------------------------------------------- /static/images/browser-extension-dom-detection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/browser-extension-dom-detection.png -------------------------------------------------------------------------------- /static/images/browser-extension-http-requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/browser-extension-http-requests.png -------------------------------------------------------------------------------- /static/images/error-message-on-different-browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/error-message-on-different-browsers.png -------------------------------------------------------------------------------- /static/images/navigator-object-on-different-browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/navigator-object-on-different-browsers.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Tracking 筆記 2 | 3 | 網站:https://web-tracking.allenchou.cc/ 4 | 5 | ## 貢獻 6 | 文章內容在 [/content/docs](https://github.com/s3131212/web-tracking-notes/tree/main/content/docs)。 -------------------------------------------------------------------------------- /static/images/string-representation-on-different-browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3131212/web-tracking-notes/HEAD/static/images/string-representation-on-different-browsers.png -------------------------------------------------------------------------------- /resources/_gen/assets/scss/book.scss_50fc8c04e12a2f59027287995557ceff.json: -------------------------------------------------------------------------------- 1 | {"Target":"book.min.c58292d36b18b675680ab9baea2029204537b839ea72f258746ec0f32ce8d6c8.css","MediaType":"text/css","Data":{"Integrity":"sha256-xYKS02sYtnVoCrm66iApIEU3uDnqcvJYdG7A8yzo1sg="}} -------------------------------------------------------------------------------- /content/docs/stateful-tracking/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: "Stateful Tracking" 4 | --- 5 | 6 | # Stateful Tracking 7 | 8 | 在第一章時,我們提過,一般而言可以將 web tracking 技術分為兩大類型:stateful 與 stateless。在 stateful web tracking 中,tracker 會預先決定好一個唯一的 identifier,並儲存在特定位置;在 stateless web tracking 中,tracker 沒辦法儲存東西,所以只能去蒐集現有資訊並算出一個 identifier。 9 | 10 | 在這個章節中,我們將討論各種 stateful web tracking 技術,討論 tracker 可以如何儲存它的 identifier。 11 | 12 | 如同前面提過,web tracking 可以根據範圍區分成 same-site 與 cross-site tracking。只要可以儲存 identifier,就一定可以作到 same-site tracking,畢竟網站 A 存進去的 identifier,網站 A 當然讀得到。不過 cross-site tracking 卻不是如此,如果要做到 cross-site tracking,追蹤者需要在不同網站維持同一個 identifier,或至少可以連結不同的 identifier,如此才能知道兩個網站的訪客是同一個人,可是這兩個途徑都不太簡單。這個章節中,我們也會不同儲存 identifier 的方法是否可能達成 cross-site tracking。 -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/battery-status-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 10 3 | title: "Battery Status Fingerprinting" 4 | --- 5 | 6 | # Battery Status Fingerprinting 7 | HTML5 提供了一些使網頁開發者得以取得硬體資訊的 API,其中一個是 Battery Status API,其原本是用來讓網站監控裝置的電池狀態,以提供更好的用戶體驗。電池狀態有許多優勢,例如,開發者可以根據使用者的電量來決定要提供高效能版本或省電版本,或當然也有可能用於一些不那麼正當的用途,例如 Uber 發現使用者在手機快沒電時傾向於接受比較高的價格1。這個功能看起來非常無害,甚至標準制訂者一開始也不認為這可能有什麼安全性威脅。然而,2015 年時,Olejnik 等人發現,該 API 也可能被用來進行 browser fingerprinting。更甚至,2016 年時 Englehardt 等人發現真的有 tracker 正在積極地使用 Battery Status API 做 fingerprinting。我們將這種技術稱為 Battery Status Fingerprinting。 8 | 9 | ## Battery Status API 10 | Battery Status API 可以提供一些關於電池的資訊,包含電量(`level`,0.0 ~ 1.0)、是否在充電(`charging`,布林值)、預期還有多久可以充滿電(`chargingTime`,整數秒)、預期還有多久會消耗完電池(`dischargingTime`,整數秒)。 11 | 12 | ![](/images/battery-api.png) 13 | 14 | ```javascript 15 | navigator.getBattery().then(console.log); 16 | ``` 17 | 18 | ## Battery Status Fingerprinting 19 | 電池資訊看似無害,但我們可以這樣想想:一個裝置的電池狀態,好像應該會是連續的,而且在各個網站上都是一樣的?如果我在充電,我的電池剩餘量一定是上升,反之則一定是下降,而且不管我在看哪個網站,都是用同一個電池,所以狀態也會一樣。換言之,藉由電池狀態好像可以作為某種短期的 identifier。 20 | 21 | Olejnik 等人於是就去研究是否可能將電池狀態用於 browser fingerprinting。我們可以觀察到,在一個非常短的時間內(大概 30 秒左右),電池狀態不會有改變,所以 `(chargingTime, dischargingTime, level)` 可以作為 identifier。有鑑於 `chargingTime` 與 `dischargingTime` 只會擇一出現,其實我們只有兩個連續的資料點和一個離散的資料點(是否正在充電),但這樣也足以做 identifier 了。 22 | 23 | 讀者可能會問:欸不是,一個只能活 30 秒的 identifier 是能幹麻?欸... 其本身不能幹麻。不過不妨這樣想,會需要 browser fingerprinting 的原因之一是 cookie 會被刪除,而追蹤者希望 cookie 被刪除後,可以藉由 browser fingerprint 把原本的 identifier 塞回去。換言之,在一些情境中,browser fingerprint 其實本身不需要是長期穩定的,只需要能從「cookie 中的 identifier 被刪掉」存活到「重新把 identifier 寫進 cookie」即可,而 30 秒去偵測到 cookie 被刪除並重新建立其實綽綽有餘。 24 | 25 | ## Battery Capacity 26 | 雖然剩餘電量沒辦法存活很久,不過電池本身的參數(充飽的滿電量、電壓等)卻是相對穩定的,其中滿電量會依照電池本身規格與消耗狀態受到影響,即便是一模一樣的電池,在經過一段時間的使用,也會有不同的滿電量。換言之,如果能回推出滿電量,就能有比較長效的 identifier 了。 27 | 28 | 舉例來說,UPower 的電量計算方法是 `100 * (Energy / EnergyFull)`,其中 `EnergyFull` 精準到小數點後四位。 29 | 30 | 我們會發現 browser 只拿得到 Energy 與 EnergyFull 的比值,還不足以知道 EnergyFull 的數字。但是,因為 UPower 會給出 64-bit double precision 的浮點數,能夠讓相除結果剛好完美地符合這個浮點數的數字組合其實不多,於是我們只要先窮舉許多可能的數字,找到所有有辦法讓 `Energy / EnergyFull` 等於該浮點數的 `EnergyFull`,隔一段時間之後再重複做一次,會找到另外一堆符合條件的潛在 `EnergyFull`,多做幾次然後 intersect 下來,就可以還原出 `EnergyFull`。不過這邊有個很重要的前提是能夠拿到原始的 64-bit double precision 浮點數,如果被處理過(例如四捨五入),數字就會不精準到難以回推出有用的資訊。 31 | 32 | ## 防禦 33 | 防禦大概可以分成兩種:降低精準度、拿掉該功能。最早的 Battery Status API 在有些平台上可以拿到 64-bit double precision 的浮點數,但現在多半只能拿到小數點後兩位(也就是百分比不會有小數),如文章一開始的圖中所示。 34 | 35 | 另外一些瀏覽器,例如 Firefox,則是直接拿掉這個功能的公開介面,只能使用 browser extension 存取;Safari 在 Olejnik 等人的研究出現時尚未實作 Battery Status API 的公開介面,所以也決定直接不實作該功能。 36 | 37 | ## 參考資料 38 | Olejnik, Łukasz, et al. "The leaking battery." _Data Privacy Management, and Security Assurance_. Springer, Cham, 2015. 254-263. 39 | Laperdrix, Pierre, et al. "Browser fingerprinting: A survey." _ACM Transactions on the Web (TWEB)_ 14.2 (2020): 1-33. -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/scheme-flooding.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 11 3 | title: "Scheme Flooding" 4 | --- 5 | 6 | # Scheme Flooding 7 | 8 | 之前我們看到可以藉由使用者安裝的 browser extension 來做 fingerprinting,於是不禁想問:電腦上裝的應用程式有沒有可能也拿來做 fingerprinting?或是更重要的,有辦法在 browser 上知道系統有哪些應用程式嗎?今天想來介紹一個很有趣的 Browser Fingerprinting 技術稱為 scheme flooding。許多應用程式有所謂的 "custom URL scheme" 或是 "custom protocol handler",例如 `skype:`, `spotify:`, `itunes:` 等,當瀏覽器看到這些 protocol handler 時,便會嘗試打開相對應的應用程式。這個功能看似好用,但可能打開新的 attack vector。假如我可以窮舉所有 protocol handler,看哪些會有反應,似乎就可以知道使用者安裝了哪些應用程式,並藉由這項資訊來做 profiling 與 tracking。這就是所謂的 scheme flooding。 9 | 10 | 大家可以嘗試到這個網站看看你的瀏覽器有沒有出賣你:https://schemeflood.com/ 11 | 12 | 但在開始之前,我想要先針對一些用字與定義做一些說明。在此文中我把 scheme flooding 定位成一種 browser fingerprinting 技術,但這樣的定義會有兩種問題。首先,scheme flooding 在發現者眼中是一種「可以達成 fingerprinting 的漏洞」,而不是單純的 fingerprinting 技術,在[有些媒體](https://www.ithome.com.tw/news/144424)上也稱之為漏洞,此處我無意開啟何謂漏洞的討論,而是只想把重點著重在:這個特性可以被用來做 fingerprinting,至於它本身是不是漏洞,並非此文討論的重點。此外,scheme flooding 是 fingerprinting 技術,但它算是 browser fingerprinting 技術嗎?這部份涉及了 browser fingerprinting 的嚴格定義,不過考慮到 scheme flooding 符合 (1) 只需要在 browser 上做運算,而沒有儲存任何內容,也就是 stateless 的,以及 (2) 其所刺探的資訊與 browser 所運行的 OS 有關(嚴格來說,OS 之中安裝了哪些程式),所以我還是想將其定義為 browser fingerprinting。如果讀者不認同我的定義,一來我很期待能聽到不同意見,二來即便此文在讀者眼中可能用字不精確,但我相信還是會有些幫助的。 13 | 14 | ## 何謂 Custom Protocol Handler 15 | 16 | 我們可以簡單地設想一個情境:當使用者瀏覽網頁時,如果可以直接打開應用程式的指定畫面,那對使用者體驗會有所改善。舉例來說,當我在某個社群媒體上看到有人分享一首歌,可以點一下就打開 Spotify 並播放這首歌,將會很方便。因此就有了 "custom protocol handler",其運作方法是,應用程式可以註冊一個 protocol handler,例如 `spotify:`,以後只要瀏覽器看到這個 protocol handler,就會打開指定的應用程式,並且把 URL 轉給應用程式,讓應用程式去完成剩下的事情。 17 | 18 | ![](/images/custom-protocol.png.png) 19 | 20 | 21 | ## Scheme Flooding 如何運作? 22 | Scheme Flooding 的核心精神在於:嘗試窮舉所有可能的 protocol handler,然後找出那些存在的 protocol handler。 23 | 24 | 聽起來很簡單,但有個大魔王是 Same Origin Policy,以及 browser 的開發者也不是笨蛋,例如 Chrome 就設下了一些限制。所以接下來來討論一些常見 browser 可以怎麼做 scheme flooding。 25 | 26 | ### Chrome 27 | Chrome 的防禦大概是所有裡面做的最好的,如果要啟用 custom protocol handler,需要 user gesture 來啟動,例如需要透過使用者點擊等。這基本上抵禦了 scheme flooding,畢竟不可能要使用者幫追蹤者點一堆連結吧。 28 | 29 | 然而 Chrome 卻開了一個後門:extension 可以設定一個 flag 繞過 user gesture 直接打開 custom protocol handler。而其中有個預設就有的 extension 是 PDF Viewer。於是只要先打開一個 PDF 檔案再打開 protocol handler,就可以繞過 user gesture 的限制。 30 | 31 | 目前這個 bug 還沒有被修正,有興趣的人可以追蹤相關 issue: [Issue 1208903: External Handler detection technique allows reliable cross-browser fingerprinting](https://bugs.chromium.org/p/chromium/issues/detail?id=1208903) 32 | 33 | 不過蠻有趣的是 Linux 反而不受影響,因為 Chrome 使用 `xdg-open` 來處理 custom protocol handler,可是在真的召喚 xdg-open 之前 Chrome 並不會知道哪些 custom protocol handler 存在。這算是種意外的收穫嗎? 34 | 35 | ### Firefox 36 | Firefox 的偵測方式也很有趣,算是一種意外的 side channel 吧。當一個 custom protocol 存在時,Firefox 會打開 `about:blank` 並跳出一個彈跳方塊問使用者是否要打開應用程式,而 `about:blank` 可以從任意 origin 存取,所以如果我在一個網頁裡面加上 `iframe` 指向特定 custom protocol,則可以讀取到 `iframe` 的內容。可是,如果一個 custom protocol 不存在,Firefox 會顯示錯誤畫面,這個錯誤畫面沒有辦法被其他 origin 讀取。於是,我只要看 `iframe` 的內容能不能被讀取即可,如果可以,代表現在 `iframe` 裡面是 `about:blank`,所以 custom protocol 存在,如果不行,則代表 `iframe` 裡面是錯誤畫面,custom protocol 不存在。 37 | 38 | Safari 也是用類似的方式偵測,所以就不贅述了。 39 | 40 | ## Scheme Flooding 的危害 41 | 相較於其他 browser fingerprinting 的技術,scheme flooding 就如同 browser extension fingerprinting 一樣有更進一步的危害。藉由使用者有安裝什麼應用程式,除了可以拿來做 fingerprinting 以外,還可以更進一步做 profiling。舉例來說,如果發現使用者有安裝某些 IDE,可能就代表該使用者是個工程師,如果使用者有裝 Slack,可能代表這台電腦是工作用的電腦等。 42 | 43 | ## 防禦 44 | 目前看來大家對於如何防禦 Scheme Flooding 還沒有太多共識。一個可能的解法是讓 custom protocol handler 存在與不存在時的反應一模一樣,但這可能還是有些問題:首先是從反應時間之類的 side channel 有機會還原出一個 custom protocol handler 是否存在;二來是這對開發者來說很不友善,他們很可能無法知道自己的 code 為何無法召喚 custom protocol handler,因為不會有任何 error 或特殊 behavior 出現(不過我還在想有沒有可能還是有 console error,但這些錯誤是 JS 看不到的)。 45 | 46 | 47 | ## 參考資料 48 | [Exploiting custom protocol handlers for cross-browser tracking in Tor, Safari, Chrome and Firefox](https://fingerprint.com/blog/external-protocol-flooding/) -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # (Optional) Set Google Analytics if you use it to track your website. 2 | # Always put it on the top of the configuration file, otherwise it won't work 3 | # googleAnalytics = "UA-XXXXXXXXX-X" 4 | 5 | # (Optional) If you provide a Disqus shortname, comments will be enabled on 6 | # all pages. 7 | # disqusShortname = "my-site" 8 | 9 | # (Optional) Set this to true if you use capital letters in file names 10 | disablePathToLower = true 11 | 12 | # (Optional) Set this to true to enable 'Last Modified by' date and git author 13 | # information on 'doc' type pages. 14 | enableGitInfo = true 15 | 16 | # (Optional) Theme is intended for documentation use, therefore it doesn't render taxonomy. 17 | # You can remove related files with config below 18 | disableKinds = ['taxonomy', 'taxonomyTerm'] 19 | 20 | title = 'Web Tracking 筆記' 21 | 22 | [markup.goldmark.renderer] 23 | hardWraps = true 24 | 25 | [params] 26 | # (Optional, default light) Sets color theme: light, dark or auto. 27 | # Theme 'auto' switches between dark and light modes based on browser/os preferences 28 | BookTheme = 'light' 29 | 30 | # (Optional, default true) Controls table of contents visibility on right side of pages. 31 | # Start and end levels can be controlled with markup.tableOfContents setting. 32 | # You can also specify this parameter per page in front matter. 33 | BookToC = true 34 | 35 | # (Optional, default none) Set the path to a logo for the book. If the logo is 36 | # /static/logo.png then the path would be 'logo.png' 37 | # BookLogo = 'logo.png' 38 | 39 | # (Optional, default none) Set leaf bundle to render as side menu 40 | # When not specified file structure and weights will be used 41 | # Deprecated, to be removed in June 2022 42 | # BookMenuBundle = '/menu' 43 | 44 | # (Optional, default docs) Specify section of content to render as menu 45 | # You can also set value to "*" to render all sections to menu 46 | BookSection = 'docs' 47 | 48 | # Set source repository location. 49 | # Used for 'Last Modified' and 'Edit this page' links. 50 | BookRepo = 'https://github.com/s3131212/web-tracking-notes' 51 | 52 | # Specifies commit portion of the link to the page's last modified commit hash for 'doc' page 53 | # type. 54 | # Required if 'BookRepo' param is set. 55 | # Value used to construct a URL consisting of BookRepo/BookCommitPath/ 56 | # Github uses 'commit', Bitbucket uses 'commits' 57 | BookCommitPath = 'commit' 58 | 59 | # Enable 'Edit this page' links for 'doc' page type. 60 | # Disabled by default. Uncomment to enable. Requires 'BookRepo' param. 61 | # Path must point to the site directory. 62 | BookEditPath = 'edit/master/exampleSite' 63 | 64 | # (Optional, default January 2, 2006) Configure the date format used on the pages 65 | # - In git information 66 | # - In blog posts 67 | BookDateFormat = 'Jan 2, 2006' 68 | 69 | # (Optional, default true) Enables search function with flexsearch, 70 | # Index is built on fly, therefore it might slowdown your website. 71 | # Configuration for indexing can be adjusted in i18n folder per language. 72 | BookSearch = false 73 | 74 | # (Optional, default true) Enables comments template on pages 75 | # By default partials/docs/comments.html includes Disqus template 76 | # See https://gohugo.io/content-management/comments/#configure-disqus 77 | # Can be overwritten by same param in page frontmatter 78 | BookComments = false 79 | 80 | # /!\ This is an experimental feature, might be removed or changed at any time 81 | # (Optional, experimental, default false) Enables portable links and link checks in markdown pages. 82 | # Portable links meant to work with text editors and let you write markdown without {{< relref >}} shortcode 83 | # Theme will print warning if page referenced in markdown does not exists. 84 | BookPortableLinks = true 85 | 86 | # /!\ This is an experimental feature, might be removed or changed at any time 87 | # (Optional, experimental, default false) Enables service worker that caches visited pages and resources for offline use. 88 | BookServiceWorker = false -------------------------------------------------------------------------------- /content/docs/stateful-tracking/firefox-tracking-protection.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 7 3 | title: "如何對付傳統追蹤技術 (IV):Firefox’s Tracking Protection" 4 | --- 5 | 6 | # 如何對付傳統追蹤技術 (IV):Firefox’s Tracking Protection 7 | 看完 Safari 的隱私保護,下一個來看 Firefox 的保護機制。Mozilla 也同樣在隱私上花了許多心力,開發了許多精彩的防禦機制。Firefox 的保護機制大概分成幾個部份,我們將依序介紹之。 8 | 9 | ## Enhanced Tracking Protection 10 | Firefox 最主要的防禦機制是 Enhanced Tracking Protection (ETP)。雖然取了一個很潮的名字,但 ETP 本質上就是 content blocking,不過 Mozilla 花了許多心思在確保網站不會因為 content blocking 而壞掉。 11 | 12 | ETP 所使用的 filter list 來自 [Disconnect](https://disconnect.me/trackerprotection),並且會去區分不同性質的 tracker(例如 social media trackers, cross-site tracking cookies, fingerprinters, cryptominers 等),此外,ETP 也有區分模式,Standard 會阻擋多數的 tracker,Strict 則會連那些躲在廣告或影片播放器裡面的 tracker 都擋掉,當然使用者也可以根據自己的需求去選擇哪些 tracker 要阻擋掉。 13 | 14 | ![](/images/firefox-etp.png) 15 | 16 | 另外 ETP 有個很重要的功能稱為 SmartBlock,其主要是想在不犧牲太多隱私的情況下提升可用性,盡可能確保網站不會因為 tracker 擋掉而壞掉。例如以 Facebook 登入的功能,許多 content blocker 會因為這有 Facebook 的 tracker 而全部封鎖,但 SmartBlock 會載入那些登入功能的 script 並持續阻擋其他 tracker,所以 Facebook 登入功能可以正常運作。 17 | 18 | ## Total Cookie Protection 19 | Total Cookie Protection (TCP) 是 ETP 的一個延伸功能。其概念是,把每個 first party 都有專屬於自己的 cookie jar(儲存 cookie 的空間),裡面會有 first-party 與 third-party cookie,且不同 first party 的 cookie jar 是隔開來。 20 | 21 | 舉例來說,`blog.example` 與 `news.example` 都使用了 `tracker.example` 的 tracker。在 `blog.example` 上,`tracker.example` 的 cookie 中有個 identifier `tracker_id=blog_123`,但是在 `news.example` 上,`tracker.example` 看不到這個 cookie,因為 first party 不同,TCP 就把 cookie 分開來儲存了。當然,如果在 `news.example` 上 `tracker.example` 又 assign 了新的 identifier `tracker_id=news_abc`,回到 `blog.example`,`tracker.example` 還是只會看到專屬於這個 first party 的 cookie,即 `tracker_id=blog_123`。 22 | 23 | 於是,就算要用 third-party cookie 追蹤,也只能追蹤到個別網站上的行為,基本上就是 reduce 成 same-site tracking 了。這是在 third-party cookie 的隱私危害與方便性之間做的權衡,直接封鎖所有 third-party cookie 可能會讓很多功能壞掉(看看 ITP 讓多少網站開發者崩潰),但顯然開放 third-party cookie 也不是個好選項,藉由 TCP,基於 third-party cookie 的功能還是可以使用,但做不到 cross-site tracking 了。 24 | 25 | ## Enhanced Cookie Clearing 26 | 基本上所有瀏覽器都有清除 cookie 的功能。當我選擇清除 `blog.example` 的 cookie 時,所有屬於 `blog.example` 的 cookie 都會被刪掉。不過傳統清除 cookie 的問題在於,它不會連 third-party cookie 都清掉。舉例來說,如果 `blog.example` 引入了 `tracker.example` 的 tracker,就算 `blog.example` 的 cookie 不見了,`tracker.example` 的 tracker 還在啊,於是當我再次造訪 `blog.example` 時,`tracker.example` 還是知道我是重複訪客。 27 | 28 | Enhanced Cookie Clearing 是 TCP 的延伸,其基於 TCP 的特性,嘗試解決這個問題。在 TCP 中,每個 first-party domain 都有自己的 cookie jar 來儲存位於該 domain 的 first-party & third-party cookie,於是,我只要把該 domain 的 cookie jar 清空即可。 29 | 30 | 沿用 TCP 的例子。當我用 Enhanced Cookie Clearing 把 `blog.example` 的 cookie 清空,會被刪除的不只有 `blog.example` 自己的 cookie,還有 `tracker.example` 在 `blog.example` 上的 cookie(`tracker_id=blog_123`)。可是同時,`tracker.example` 在 `news.example` 上的 cookie(`tracker_id=news_abc`)完全不會被影響到。 31 | 32 | 藉由這樣的設計,使「清空 cookie」功能變成真正意義上的清空 cookie。 33 | 34 | ## Redirect Tracking (Bounce Tracking) Protection 35 | Firefox 同樣有針對 bounce tracking 的防禦機制,他們稱之為 Redirection Tracking Protection。首先,ETP 已經有一系列 tracker 的名單,其中有些可能是個 domain,姑且稱之為 tracking domain。Firefox 會去檢查這些 tracking domain 裡面有沒有哪些 domain 是有 cookie 或使用其他 storage,但其實根本沒與其互動過的。試想如果一個 tracking domain 只作為 bounce tracker,使用者一造訪該 tracking domain 就會立刻被 redirect 走,不會與其有互動。因此如果有些 tracking domain 有使用 storage,使用者卻沒與之互動過,大概就很可能是 bounce tracker。Firefox 會把那些超過 45 天沒互動的 tracking domain 的 storage 清空。 36 | 37 | 38 | ## 結語 39 | 可能會有讀者疑惑,為什麼我不討論 Brave 的隱私保護機制,明明 Brave 才是花最多心力在阻擋 web tracking 上的瀏覽器。Brave 的確做得很好,不過因為 Brave 的隱私保護機制實在太多而且又太複雜,把它講完可能鐵人賽都比完了,更不用說其所需要的背景知識多到實在沒辦法三言兩語帶過。考慮到 Brave 的市占率比較低,且 Safari 與 Firefox 基本上已經涵蓋了多數主流的 anti-tracking 技術,所以只挑了這兩個 browser 出來討論。至於 Brave 的機制中比較有趣且獨特的部份,有些我在以前的文章中已經提過了,有些我在未來的文章可能也會零碎地提到,雖然可能沒有專文介紹,但應該還是會碰到不少值得一提的技術。 40 | 41 | 至此,傳統 stateful web tracking 方法及其防禦已經討論得差不多了。從下一篇開始我們會進入全新的單元:browser fingerprinting。 42 | 43 | ## 參考資料 44 | [Enhanced Tracking Protection in Firefox for desktop](https://support.mozilla.org/en-US/kb/enhanced-tracking-protection-firefox-desktop) 45 | [Firefox rolls out Total Cookie Protection by default to all users worldwide](https://blog.mozilla.org/en/security/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/) 46 | [SmartBlock for Enhanced Tracking Protection](https://support.mozilla.org/en-US/kb/smartblock-enhanced-tracking-protection) 47 | [Firefox 91 Introduces Enhanced Cookie Clearing](https://blog.mozilla.org/security/2021/08/10/firefox-91-introduces-enhanced-cookie-clearing/) 48 | [Redirect tracking protection - MDN](https://developer.mozilla.org/en-US/docs/Web/Privacy/Redirect_tracking_protection) -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/audio-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 6 3 | title: "WebAudio Fingerprinting" 4 | --- 5 | 6 | # Audio Fingerprinting 7 | Audio fingerprinting 是指涉利用 Web Audio API 來做 browser fingerprinting 的技術。Web Audio API 是個專門用於在 browser 處理音訊的 API。一如往常的,有人發現 Web Audio API 在不同的軟硬體設備上會產生不同的結果,而且在同一台電腦的同一個瀏覽器上,所產生的結果很穩定與持久,所以可以用來做 fingerprinting。 8 | 9 | ## Web Audio API 10 | Web Audio API 是一個在 browser 中處理音訊的 API,其可以建立音訊、載入現存音訊、加入特效等。整個操作流程都是在 [AudioContext](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) 底下完成,以 audio nodes(音訊節點)作為音訊作業的基本單元,這些 audio nodes 可以互相連接,建構出 audio routing graphs(音訊路由圖)。Audio routing graph 起於一個或多個音訊來源(可以是動態產生的或載入現存檔案),音訊來源會將音訊取樣,切成很多個 sample。接著,音訊的 sample 會順著路由經過各種處理,例如調整音量、加入特效、混音等。當音訊已經走完所有處理程序,最後就會走到整個 audio routing graph 的終點:可能是播放出來或是儲存進一個 buffer。 11 | 12 | ## Audio Fingerprinting 13 | 接著我們把每個細節都拆開來看,看看每個步驟可以怎麼操作,以達到 audio fingerprinting。 14 | 15 | AudioContext 是整個處理流程的 context,其要求音訊最後的去處必須是個音訊輸出硬體設備,像是音響或是耳機。但在 browser fingerprinting 的情境下,我們不希望聲音真的被播出來,只想看到它處理完成的結果。此時可以使用 OfflineAudioContext,它會將音訊處理完之後儲存到 in-memory 的 buffer 內。這個 buffer 名為 AudioBuffer,其內部使用 linear PCM 並正規化到 [-1, 1]。 16 | 17 | ```javascript 18 | const context = new OfflineAudioContext(1, 5000, 44100); 19 | ``` 20 | (本文所使用的範例 code 與參數主要來自 [fingerprint.js](https://github.com/fingerprintjs/fingerprintjs/blob/3201a7d61bb4df2816c226d8364cc98bb4235e59/src/sources/audio.ts),會做一些微調) 21 | 22 | 目前為止我們有 context 也知道音訊的目的地去哪了,接著要回過頭來檢視音訊從哪來的。我們當然可以載入一個現成的檔案,但以 fingerprinting 來說這樣太麻煩了,其實重點只是要有個音訊而已,所以用個 Oscillator(震盪器)去產生一個正弦波即可。 23 | 24 | ```javascript 25 | const oscillator = new OscillatorNode(context, { 26 | type: 'sine', 27 | frequency: 10000 28 | }); 29 | ``` 30 | 31 | 目前為止的音訊大概長這樣: 32 | 33 | ![](/images/audio-before.png) 34 | 35 | 接著,我們想要盡可能對音訊做各種莫名其妙的處理,越多處理,不同軟硬體就有可能產出越多不一致的結果。為求簡單,此處用個基本的 [Compressor](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createDynamicsCompressor) 演示即可。Compressor 主要用於把音訊中最大聲的部份往下壓,藉此避免爆音,但它一般用於什麼不重要,重點是因為 Compressor 會對音效做一些處理,而我們想要捕捉到這些處理之後微小的落差。 36 | 37 | ```javascript 38 | const compressor = new DynamicsCompressorNode(context, { 39 | threshold: -50, 40 | knee: 40, 41 | ratio: 12, 42 | reduction: -20, 43 | attack: 0, 44 | release: 0.25 45 | }); 46 | ``` 47 | 48 | 經過一連串的處理,現在的音訊長這樣: 49 | ![](/images/audio-after.png) 50 | 51 | 最後把兩者串在一起 52 | ```javascript 53 | oscillator.connect(compressor); 54 | compressor.connect(context.destination); 55 | ``` 56 | 57 | 然後令 audio context 動起來,去取每一個 sample 的值,拿去算出一個 fingerprint。 58 | 59 | ```javascript 60 | oscillator.start(0); 61 | context.oncomplete = (event) => { 62 | const samples = event.renderedBuffer.getChannelData(0); 63 | console.log(getFingerprint(samples)); 64 | }; 65 | context.startRendering(); 66 | ``` 67 | 68 | 最後實作一下算 hash 的功能,坦白說也沒什麼黑魔法,取個絕對值的和就可以了。 69 | ```javascript 70 | function getFingerprint(samples) { 71 | return samples.reduce((ps, s) => ps + Math.abs(s), 0); 72 | } 73 | ``` 74 | 75 | 同樣是 Firefox,我的 Linux 拿到的數值是 `344.5930904112756`,Windows 拿到的是 `344.5930906422436`。在 Linux 的 Chrome 上則是拿到 `1169.8477467813227`。在三個情境下,無痕模式與一般模式拿到的 fingerprint 都是一樣的。 76 | 77 | ## 為何 Web Audio API 會產生不同結果 78 | 我們必須再次提出同樣的問題:為什麼同樣一段 code 會產生不同結果?就表象上來說,因為不同軟硬體下,oscillator 產生的波形有非常些微的差異,若直接把數值印出來便可看出差異。 79 | 80 | 就表象上來說,因為不同軟硬體下,oscillator 產生的波形有非常些微的差異: 81 | ![](/images/oscillator-sample.png) 82 | (左邊是 Chrome,右邊是 Firefox,使用相同的電腦產生頻率 100 的 sine wave,取樣率為 50) 83 | 84 | 基本上主流 browser engine(Chrome 的 Blink、Safari 的 Webkit、Firefox 的 Gecko)目前的 Web Audio API 實作都是基於早期 Webkit 的實作,但隨著時代演變,各個 browser engine 都陸陸續續地加上自己的 implementation,不同的 implementation 可能會有不同結果(試想同樣一個算式,使用浮點數計算時,順序不一樣就有不同結果了),這導致不同 browser 的 behavior 開始有些微的落差。 85 | 86 | 在硬體層面,為了改善處理速度,有些 browser 會針對特定平台做最佳化。例如 Blink 在 Mac 上有[特殊的 FFT 實作](https://github.com/chromium/chromium/blob/55d9bda1b972b1488b1bb202e01a7ef7b6fff937/third_party/blink/renderer/platform/audio/mac/fft_frame_mac.cc),也有對不同 CPU architecture 做特別處理,像是 x86 上會使用 [AVX 指令集](https://github.com/chromium/chromium/blob/55d9bda1b972b1488b1bb202e01a7ef7b6fff937/third_party/blink/renderer/platform/audio/cpu/x86/vector_math_avx.cc)。此外,這個計算過程中也大量地使用了浮點數,所以計算精度也一樣會影響結果。 87 | 88 | ## 解法 89 | Tor Browser 的解法總是簡單而暴力:預設停用 Web Audio API。 90 | 91 | Brave 的主要防禦方法與其對 canvas fingerprinting 的防禦雷同:針對每個不同的 eTLD+1,都加上不同的但固定的雜訊。在不同 eTLD+1 使用不同 noise,是為了保證了 audio fingerprinting 無法用於 cross-site tracking。在相同 eTLD+1 必須使用相同雜訊,因為如果每次雜訊都不一樣,可以藉由多次取樣抵銷雜訊。 92 | 93 | ## 參考資料 94 | [Web Audio API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) 95 | [How the Web Audio API is used for audio fingerprinting](https://fingerprint.com/blog/audio-fingerprinting/) -------------------------------------------------------------------------------- /content/docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | bookFlatSection: true 4 | title: "Web Tracking 介紹" 5 | --- 6 | 7 | # Web Tracking 介紹:什麼是 Web Tracking?以及關於這系列文章的雜事 8 | 萬事起頭難,我想文章的起頭大概更難吧。這一系列的文章將會介紹有關 web tracking 各種你不需要的、對你人生毫無幫助的冷知識。我們將從最基礎的概念開始講起,從追蹤技巧講到反追蹤技巧,從基本技術講到奇技淫巧,然後再討論到新世代的追蹤技術長怎樣。 9 | 10 | ## 什麼是 Web Tracking 11 | Web tracking 是指「在 web 上面追蹤使用者」的行為。 12 | 13 | 稍微 formally 一點來講,web tracking 是「藉由為每個使用者指定或計算出一個(唯一的) identifier,在未來遇到同一個使用者時,以此辨認出這是哪個使用者」的一系列的技術。這裡所說的辨認重複使用者,不一定要是在同一個網站辨認,也關注不同網頁之間的重複使用者辨識。 14 | 15 | 舉個例子,我是一個廣告商,使用者在 `blog.example` 瀏覽了一台筆電的開箱文,於是我就指定這個使用者的 identifier 是 `userA`,並猜想他對這台筆電有興趣。當使用者又在 `blog.example` 繼續瀏覽了一個耳機的開箱文,於是我又記錄下來他喜歡耳機。至此都還在同個網站上追蹤使用者,即 same-site tracking。當使用者瀏覽 `news.example` 時,藉由 web tracking 技術,我發現他就是 `userA`,正是之前在瀏覽筆電開箱文的那個人,於是就可以在 `news.example` 投放廣告吸引他購買這台筆電,這就是 cross-site tracking。 16 | 17 | Cross-site tracking 的危害通常被認為大於 same-site tracking 的危害。讓網站管理員知道我瀏覽他的網站的哪些文章可能不是大問題,尤其如果我有登入帳號時,網站管理員肯定知道我是誰,我在看什麼。不過,如果無論我在瀏覽哪個網站,Facebook 都可以看到我的瀏覽紀錄,那大概就是個大問題了。所以目前許多封鎖 tracking 的方法都更重視抵禦 cross-site tracking,至於 same-site tracking,因為傷害較小而且更難避免,所以討論會相對少一些。 18 | 19 | 早期的 web tracking 通常是嘗試把一個唯一的 identifier 儲存在使用者的瀏覽器中,以後只要檢視該 identifier 便可以知道使用者是誰。通常這種需要在瀏覽器中儲存資料的技術被稱為 stateful tracking。但 stateful tracking 有個問題:如果使用者把 identifier 刪掉,就追蹤不到他了。於是之後有人在想,如果不存資料在瀏覽器中,而是利用一些特徵去「算出」identifier,使用者就沒辦法把東西刪掉了,至此我們進入 stateless tracking 的時代。 20 | 21 | 在 stateless tracking 的技術中,最知名也最廣泛被使用的是 browser fingerprinting。Browser fingerprinting 利用了瀏覽器、OS、硬體等的各種資訊,去算出一個 identifier,因為這些資訊不一定會常有變動(至少多數人不會每三天換一張顯卡吧),所以每次算出來的 identifier 的課都有很高的機率是一樣的。 22 | 23 | ## 所以這跟 Security 有什麼關係? 24 | 我想多數人看到 web tracking 的第一個反應是:所以這跟 Security 有什麼關係啊?為什麼不去 Modern Web 組。 25 | 26 | 其實我也不知道欸。可能因為 Modern Web 看起來好競爭喔,所以想來 Security 的溫暖小圈圈取暖吧。 27 | 28 | 稍微認真一些回應的話,一般來說,資安(security)與隱私(privacy)是難以分割的,例如藉由資安的技術保護隱私(個人資料等),隱私的洩漏可能創造新的資安危機,所以我們常把 security 與 privacy 合併使用。我認為不應該把 security 狹隘地想像成駭進別人的電腦然後提權拿到 root,其他重要的問題,像是如何維持通訊安全(e.g. HTTPS)以及在網路上保持匿名(e.g. Tor),或是在資訊不洩漏的情況下做到很多功能(e.g. privacy-preserving 的應用、密碼學黑魔法),也都是資安的一環。 29 | 30 | 也許 web tracking 不像是其他典型資安領域的 attack & defense,但從前述對於 web tracking 用途的論述可以看出,這些技術可能造成隱私危害,讓使用者在網路上無所遁形,而這樣的隱私洩漏可以是種資安威脅。希望我有成功說服大家。 31 | 32 | 如果很疑惑這些隱私洩漏有什麼關係,或甚至覺得隱私不重要,我不認為自己能用三言兩語說服你,也不覺得說服大家隱私很重要是這系列文章的目的,但我很推薦 Shoshana Zuboff 的《監控資本主義時代》,這本書做了許多論述。 33 | 34 | ## 我為什麼需要知道 Web Tracking 相關技術? 35 | 同學,你問錯問題了。比較好的問題是:我需要知道 web tracking 相關技術嗎?為什麼? 36 | 37 | 很遺憾的,你不需要知道 web tracking 相關技術。除非這是你在研究 browser privacy 或是你就在網路分析公司工作,否則這些知識本身對你的人生大概不會有什麼幫助。 38 | 39 | 不過我想來分享一下自己的學習心得。Web tracking 技術最有趣的點在於發現一些看似正常的設計竟然可以被如此運用,看似完備的防禦可以被如此繞過,那種「還可以這樣搞喔天啊」的驚奇感也就油然而生。此外,在研究這些技術的過程中,我也開始會去注意每一個功能的設計有沒有可能 leak 一些意想不到的資訊,這些資訊可能被怎麼運用。 40 | 41 | 當然,這只是我非常個人的收穫,讀者可能會有不一樣的收穫,可能學到更多東西,可能找到新的興趣,可能會覺得奇怪的知識增加了,也可能讀完之後只覺得自己浪費時間讀一些不知道能做什麼的東西。每個人的學習經驗與歷程都是不一樣的,但也正是如此,世界才如此有趣吧。 42 | 43 | 44 | ## 系列文章結構 45 | 希望我有成功說服讀者嘗試把這系列的文章看下去,至此終於可以來介紹這一系列的文章會寫什麼了。 46 | 47 | 除了開頭與結尾以外,系列文章大概可以分成三大部份: 48 | - Stateful Tracking:介紹一些早期與當代的 stateful tracking,以及這些 stateful tracking 可以如何被避免,而這些阻擋 stateful tracking 的手段又如何被繞過與攻擊。 49 | - Browser Fingerprinting:介紹各種 browser fingerprinting 的技術以及其反制方法,其中會再分成常見的、廣泛被使用的方法,以及特殊的甚至奇葩的方法。 50 | - Privacy-preserving Web Tracking:如果我們一方面想要做 tracking,但同時又想保護使用者的隱私,我們可以怎麼做?目前有許多解法,但還沒有收斂到一個大家共通的標準,也可能以後都不會收斂。我會挑選幾個值得討論的例子來講。 51 | 52 | 每一個部份會寫幾篇文章,會舉哪些例子,雖然心中大概有個圖像了,但還沒有很肯定,所以就先不放出來了。等系列文章寫完之後再回頭修改這段文字吧。 53 | 54 | 原則上每篇文章之間連貫性不高,如果真有連貫性,我也會盡可能地明顯讓讀者知道這篇文章需要前面哪些文章作為基底,所以可以跳著讀,應該不會有太大的問題。 55 | 56 | ## 預設背景知識 57 | 很遺憾的(?),閱讀這系列的文章會需要一些基本知識。 58 | 59 | 在一開始介紹 stateful tracking 與 browser fingerprinting 時,會需要 HTTP 與 JavaScript 的基礎,基本上只要有寫過網頁前端或是打過 web 類型的 CTF 題目就非常夠用了。文章中提到的多數技術,都會在第一次提及時做 high-level 的介紹,但這只是幫已經對該技術有基礎認識的讀者回憶起那是什麼,完全沒聽過該技術的讀者很可能聽不懂我的介紹。如果真的缺乏什麼背景知識,網路上也都有相關資源可以閱讀(尤其推薦 [MDN](https://developer.mozilla.org/)),不用擔心會需要花費很大心力才能跟上。有鑑於這系列文章的定位是會基本的 web 就要能看懂,如果讀者需要花費很大心力跟上進度,那大概就是我寫得不夠淺顯易懂吧,還請讓我知道哪裡可以改進,謝謝。 60 | 61 | 後面會介紹 privacy-preserving web tracking,其中有些技術可能會應用到專業領域的知識,像是密碼學或是 Machine Learning。兩個領域也都有許多網路上的資源可以參考,但可能會稍微難一些。 62 | 63 | ## 自我介紹 64 | 突然想到還沒介紹自己是誰呢。不過我知道沒人在乎我是誰。如果真的要介紹,我是個不學無術文筆奇差還自以為有能力寫完這一系列文章的笨蛋。期許一個月後的自己真的有能力寫完這系列的文章,不是只會打嘴砲。 65 | 66 | ## 一點結語 67 | 在一篇楔子裡面寫結語,是個很有趣的體驗呢。 68 | 69 | 我想提醒讀者,技術不是中立的,我也不是中立的,我更沒有打算假裝自己很中立。我的立場很鮮明:web tracking 會對隱私造成傷害,這些技術是有害的,我們要反制它們。在後面文章的論述,我對於 web tracking 的論述也是以他們有害為論述核心。期許讀者在閱讀的同時,小心地注意文章中明示或暗示的價值,並且時刻反思我講的真的有道理,不要輕易被我帶風向了。 70 | 71 | 此外,我不是相關領域的專家,也就只是讀了幾篇文章而已,對這個領域的認識淺薄到實在不足掛齒,更遑論拿來說嘴,寫這系列文章也只是想整理過去自己所學。如果我有寫錯,還希望各位先進能不吝指教。然後這系列的文章會有一點晶晶體,我嘗試過盡可能講中文,但有些名詞翻譯成中文感覺就是不對,所以就覺得算了就這樣吧,畢竟 web tracking 真的很 amazing,翻成 Chinese 整個 meaning 就偏掉了。 72 | 73 | 希望大家會喜歡這系列的文章,也期許我自己可以順利完賽。如果有任何意見或想要交流,都可以留言或寄信給我(s3131212 at gmail dot com)。 -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/ad-blocker-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 8 3 | title: "Ad Blocker Fingerprinting" 4 | --- 5 | 6 | # Ad Blocker Fingerprinting 7 | 8 | 曾經我們討論過,阻擋 tracker 的方法中,最廣泛被使用的是 filter list,也就是去列出我想要阻擋的黑名單,然後 ad blocker 會根據 filter list 去阻擋 tracker。Ad blocker 不但可以改善使用體驗,還可以藉由阻擋 tracker 來減少隱私侵害。Ad blocker 的運作方式已經在前文討論過,簡單來說,ad blocker 會阻擋 filter list 上指定的 HTTP request 以及隱藏指定的 DOM element,而這些改變是可以被有效偵測的。 9 | 10 | 另外,在 Ad blocker 的世界中,並不存在一個世界統一的 filter list,雖然會有幾個最大咖的(e.g. Easylist),但也會有眾多不那麼知名的但同樣好用的(e.g. Peter Lowe’s Ad and tracking server list)、針對特定語系的(e.g. AdGuard Japanese)、針對特定類型內容的(e.g. Online Malicious URL Blocklist)。如果全部 filter list 都啟用,可能造成效能不彰,所以使用者會根據自己需求去挑選他需要的 filter list,或是 ad blocker 可能會根據系統語言去自動地啟用相對應的 filter list。於是作為中文使用者,我會使用針對中文網站的 filter list(e.g. Easylist China),但不會去用針對德文網站的 filter list(e.g. EasyList Germany),反正我也不會瀏覽德文的網站。 11 | 12 | ![](/images/ublock-origin-filter-lists.png) 13 | 14 | 值得注意的是 ad blocker 除了基於 filter list 的方法以外,也有基於 Machine Learning 或 heuristic 的,但因為主流的還是 filter list,所以此文還是直接稱呼這些基於 filter list 的 ad blocker 為 ad blocker。 15 | 16 | 至此已經可以看出問題所在:如果每個使用者啟用的 filter list 不一樣,tracker 則可以根據使用者啟用了哪些 filter list 去識別使用者,而且哪些 filter list 有被啟用,可以使用網頁行為改變的狀態來得知,於是這就成了可供 fingerprinting 的資訊來源。當然我們可以想像多數使用者都不會去改 ad blocker 的設定,而且同個語系的人使用的 filter list 大概也都差不多,但這仍然是個資訊洩漏。 17 | 18 | ## 如何從 Ad Blocker 獲得資訊 19 | 正如之前介紹過的,ad blocker 主要分成兩種不同策略:阻擋 requests(resource blocking)與隱藏/刪除 elements(element hiding)。這兩者也就對應到兩種偵測策略:去檢視哪些 request 被阻擋了(resource blocking),以及去檢視哪些 elements 被隱藏了(element hiding)。 20 | 21 | ### 從 resource blocking 取得資訊 22 | 如果我們現在有 A, B, C 三個 filter list,想要找出每個使用者用了哪些 filter list,可以這麼做: 23 | 1. 預處理:找出 A, B, C 分別有哪些 URI 是只有該 filter list 有阻擋,其他都沒有的。 24 | 2. 嘗試發一個 request 到該 URI,看會不會成功 25 | 3. 如果 request 成功,則該 filter list 沒有啟用,反之,如果 request 被阻擋則代表可能有啟用該 filter list 26 | 27 | 如果沒有一個 URL 是只存在於唯一 filter list,也還是可以用排容原理來找出哪些 filter list 有被啟用。可能已經有讀者注意到:其實我們根本不需要還原出哪個 filter list 有被啟用,只需要注意哪些 URL 有被阻擋即可。但這麼做的壞處是,如果 filter list 內容有所變動時,以 filter list 啟用狀態作為判別依據的 fingerprint 將不會改變,以 URL 阻擋狀態作為判別依據的 fingerprint 則會出現系統性的偏移。 28 | 29 | 不過這方法有個顯然的缺點:發 HTTP request 很耗時、耗能、佔用頻寬,而且使用者如果看到一個網站不斷在載入各種資源,也很可能會起疑。如果每個 filter list 都有一個唯一的 URI 是別人沒有的,那也許還可以接受,但如果要使用排容原理,所需的請求量很快就會飆升上去。另外,請求失敗的原因很多,可能是網路不穩定、遭防火牆攔截等,不一定是 ad blocker 造成的。所以一般來說不會用 request blocking 來做 fingerprinting,畢竟我們有效率更好的方法。 30 | 31 | ### 從 element hiding 取得資訊 32 | 第二種策略是利用 element hiding 取得資訊,也就是去檢驗哪些 element 會被隱藏。 33 | 34 | 在執行上也和 request blocking 類似,假設我們現在有 A, B, C 三個 filter list,想要找出每個使用者用了哪些 filter list: 35 | 1. 預處理:找出 A, B, C 分別有哪些 element 是只有該 filter list 有阻擋,其他都沒有的。通常我們會拿到一個 HTML class 或 id 或 tag name 等資訊。 36 | 2. 嘗試在網頁上建立符合條件的空白 element(例如賦予指定的 class 或 id 等),並檢驗該 element 有沒有被隱藏 37 | 3. 如果該 element 正常顯示,則該 filter list 沒有啟用,反之,如果 element 被隱藏了,則代表可能有啟用該 filter list 38 | 39 | Tracker可以利用 `offsetParent` 檢驗一個 element 是否正常顯示,當一個 element 或是其 parent 被隱藏時,它的 `offsetParent` 會是 null。 40 | 41 | 不過這個方法並不是萬無一失的。網站可能自己有 CSS 或是一些 script 去處理這些 element,可能會導致錯誤判斷。例如,如果 CSS 裡面把特定 element 設定為 `display: none`,則會造成 false positive,tracker 誤以為 filter list 有啟用,但其實是網站自身把該 element 隱藏了。一個簡單的解法是準備很多個預期會被阻擋的唯一 element,如果這些 elements 之中超過一定比例被隱藏或顯示,就判斷說該 filter list 有被啟用或沒有被啟用 42 | 43 | ## 優勢與限制 44 | 將 Ad blocker 的行為作為 fingerprinting 的資訊來源,對追蹤者來說有幾個優勢。首先,因為安裝 Ad blocker 的人佔有相當比例,所以許多人都會被影響到。此外,如果在隱私模式下有啟用 Ad blocker,則因為在一般模式與隱私模式下 Ad blocker 的行為應該是一樣的,所以在兩種模式下得到的 fingerprint 理應是一樣的,進而可以追蹤那些使用隱私模式的使用者。 45 | 46 | 然而 Ad blocker 也有幾個限制。首先,許多瀏覽器(Chrome Desktop 與 Firefox Desktop)預設禁止 browser extension 在隱私模式下運作,所以 Ad blocker 在隱私模式下是預設關閉的。不過 Safari 就是預設隱私模式下一樣可以執行 extension,所以不會被影響到。另一個限制是,filter list community 很頻繁地在更新 filter list,所以追蹤者也要積極地更新欲探測的 request 或 element 列表,雖然使用前述的比例法(只有超過一定比例時才認定 filter list 有啟用)可以延長一份 element 列表可用的壽命,但終究必須隨著 filter list 改動而有所更新。 47 | 48 | ## 防禦 49 | 目前要防禦 ad blocker fingerprinting 主要還是靠 content blocking,也就是一開始就把 tracker 阻擋掉。這會陷入一個很諷刺的狀況:如果阻擋 tracker 失敗,則阻擋 tracker 這個行為本身反而會幫助 tracker 拿到更多資訊。後面在系統性地討論 browser fingerprinting 防禦時,會討論到所謂的 privacy paradox,此處的狀況正是一種 privacy paradox 的案例。 50 | 除了最廣泛的防禦手段以外,也有針對 ad blocker fingerprinting 的防禦手段。如果仔細檢視,會發現「以 ad blocker 做 fingerprinting」與「偵測 ad blocker 的存在」其實是非常類似的問題,只是 ad blocker fingerprinting 還額外要求了偵測各個 filter list 的啟用狀態。於是,我們可以把問題簡化成:如何避免 ad blocker 被發現,畢竟只要連 ad blocker 存在都無法被偵測到,就不可能知道有哪些 filter list 正在被使用了。此處可以回到之前討論 [browser extensions fingerprinting]({{< relref "/docs/browser-fingerprinting/techniques/browser-extensions-fingerprinting" >}}) 的防禦,畢竟 ad blocker 也是一種 browser extension,也就可以使用 browser extension fingerprinting 的防禦手段阻擋 ad blocker fingerprinting 。 51 | 52 | 最後,除了消極地避免被偵測到以外,也可以積極的防禦:動態地去偵測那些可能是在探察 Ad blocker 是否存在的 script,然後積極地封鎖。這裡的動態偵測可以是用一些 heuristic 去判斷,也可以建立 filter list 來阻擋,例如 [Anti-Adblock Killer](https://github.com/reek/anti-adblock-killer),不過它看起來已經沒有在維護了。 -------------------------------------------------------------------------------- /content/docs/privacy-preserving-web-tracking/unified-id-2.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: "Unified ID 2.0" 4 | --- 5 | 6 | # Unified ID 2.0 7 | 之前介紹的幾個技術都是嘗試在不需要識別出特定使用者的情況下推測使用者興趣(例如 FLoC 或 Topics API)或完成 conversion measurement(例如 PCM 與 IPA)。這些技術的目標都是想要用隱私保護的方式達成以前需要 web tracking 才能達成的功能。但此章要介紹的 Unified ID 2.0 採取了一條完全不同的路徑,他們認為傳統 third-party cookie 追蹤的問題在於這些追蹤往往並未獲得使用者同意,即便有同意,使用者也難以管理繁雜的隱私設定,因此新世代的 web tracking 該達成的是只有在使用者同意下可以進行追蹤,而且必須提供使用者足夠簡單且統一的方式管理他們的隱私設定。 8 | 9 | Unified ID 2.0 (UID2) 是一個開源框架,可供網站、廣告商和數位廣告平台使用,以建立身分識別。隨著Google宣布計劃在2022年在 Chrome 上封鎖 third-party cookie,The Trade Desk迅速開啟了Unified ID 2.0的開發,嘗試在沒有 third-party cookie 的世界繼續達成 cross-site tracking。與傳統 third-party cookie 不同的是,Unified ID 2.0 將要求使用者在網站管理者建立 UID2 identifier 之前,需要明確地同意網站的隱私權政策(或其他條款),並提供其 email address。此外,會有一個集中化的管理單位負責 key management,而且使用者可以直接在這個單位上登記希望退出(opt-out)追蹤,已達成更友善的隱私管理。 10 | 11 | ## Unified ID 2.0 如何運作? 12 | UID2 的角色除了傳統廣告產業就有的那些以外,還額外有 UID2 administrator 與 UID2 operator。UID2 administrator 是一個集中化的服務,負責管理 UID2 ecosystem。他們的工作包括 key management,即產生與發布 key 給 UID2 operator 與 DSP。此外,UID2 administrator 還需要處理使用者的退出要求,並通知 UID2 operator 和 DSP。UID2 運營商則是負責生成和管理 UID2 及 UID2 token 所需基礎設施的組織。UID2 operator 從 UID2 administrator 那裡拿到 key 與 salt 。網站發布者可以可以通過 UID2 operator 的 API 請求 UID2 或 UID2 token。 13 | 14 | 具體來說,整個運作流程大致如下: 15 | 16 | ![](/images/Unified-ID-2.0.jpg) 17 | 18 | 當使用者訪問一個部屬 UID2 的網站時,發佈商會向使用者解釋使用者為何需要允許追蹤、如此可以得到什麼(例如此時可以要求使用者同意追蹤以換取免費存取網站內容,或是付費存取網站內容),並要求使用者提供 email address。網站接著會把這個 email address 傳送給 UID2 operator,並在 UID2 operator 經過加鹽和 hash 過後,轉換成一個唯一的 identifier 並回傳給網站,網站會將其儲存在 first-party cookie 或是其他儲存空間中,我們稱之為這個唯一的 identifier 為 UID2。於是以後使用者再次造訪時,這個網站都可以從其 cookie 中得知使用者身分。 19 | 20 | UID2 是廣告商會實際看到與用於追蹤與廣告投放的 identifier,不過因為是拿 email address 加鹽後 hash 過的,而且只有 UID2 administrator 與 UID2 operator 知道 salt 為何,所以其他單位(例如 SSP 與 DSP)無法還原出 email address 本身。 21 | 22 | 當網站想要推送廣告時,它會將使用者的 email address 傳送給UID2 service,由 UID2 operator 生成一個一次性的 UID2 token 並回傳,這個 UID2 token 是直接將 UID2 加密,也就是 Enc(UID2) = `Enc(Hash(salt, e-mail))`(白話文來說,將 email address 加鹽後 hash 再加密) 。接著,網站將該 UID2 token 連同其他競標資訊傳送給潛在的廣告商 SSP,SSP 會再將競標資訊與 UID2 token 轉送給 DSP。DSP 得到 UID2 token 後向 UID2 operator 請求 key 來解密,還原出 UID2,並以此作為 identifier 來追蹤或是做廣告競標。 23 | 24 | 為了讓使用者更容易管理隱私設定,UID2 使用一個集中化的平台管理使用者的追蹤意向,我們稱之為 UID2 administrator。如果使用者希望特定平台停止使用他的資料,他可以提供自己的 email address,UID2 administrator會通知 UID2 operator,然後 UID2 operator 會將 hash 過的 email address 儲存下來,若以後有網站請求這個 email address 的 UID2 與 UID2 token 時,則不會回應真正的數值。此外,UID2 administrator 也會通知 DSP特定 UID2 已經選擇退出追蹤,DSP 若再遇到這個 UID2 時(例如網站可能有在使用者退出前便 cache 下來的 UID2 token),應該將其移除在競標邏輯之外。 25 | 26 | UID 2.0 設計的一個核心理念是要建造假名(pseudonym),這個假名無法讓廣告商連回使用者真正的身分。因此,UID2 operator 在產生 UID2 時會加鹽,而且 salt 是由一百萬個 salt bucket 中隨機選一個出來的,如此可以確保 UID2 難以被還原回 email address。此外,對於個別使用者而言,每年都會重新抽一個 salt bucket 並以此產生新的 UID2。一個合理的推測是如此可以避免使用者被長時間的追蹤,但這麼做效果如何仍然未知。 27 | 28 | ## 安全分析 29 | 目前的設計足以確保在不考慮 key compromise 的情況下只有獲得授權的單位可以追蹤使用者。由於每次訪問都會提供新的UID2 token,也就是加密 UID2 + random nonce,因此對於那些無法解密 token 的人而言,UID2 token 本身無法用於追蹤, 只有得到授權的參與者可以正確地解密 UID2 token 得到穩定的 UID2。此外,因為有加鹽並 hash,而且定期改變 salt bucket,所以直接從 UID2 還原出 email address 的可能性並不高,藉此確保廣告商永遠都只能看到使用者的假名,不會知道真正身分。 30 | 31 | 然而,UID 2.0 的設計有幾個可能的安全性風險。首先,因為 UID2 operator 用於加解密 UID2 的 key 都是一樣的,所以一個惡意的 DSP 可以洩漏 decryption key 給其他與之串通的人,使他們可以解密他們本來不該可以解密的 UID2 token。而且目前的設計並沒有辦法辨認出誰是惡意的 DSP 也沒辦法追蹤 UID2 洩漏的狀況。此外,目前的 UID2 token 產生過程欠缺驗證機制,使得任何得到 key 的人都可以偽造 UID2 token。一個理想的情境是 UID2 operator 應該為 UID2 token 提供簽章以保證其完整性與可驗證性。 32 | 33 | ## 隱私分析 34 | 至此應該已經可以了解為何在文章開頭指出,UID 2.0 走了一條完全不一樣的路。他們想做的並不是消滅個人化的追蹤,而是在得到使用者的同意下進行個人化的追蹤,而且還是 cross-site 甚至 cross-device tracking。這種積極取得同意追蹤的措施是否算是一種保護隱私,取決於大家對於隱私的定義。也許和直覺有些衝突,但這種強調個人選擇與獲得同意的方法,正是當代許多國家的立法方向。專精隱私權的知名法學家 Daniel J. Solove 將其稱為 privacy self-management,指法律賦予人們一系列的權利,讓他們能夠自行決定如何權衡蒐集、使用、揭露資料的成本和利益,更甚至,人們的「同意」(consent)幾乎成為了合法蒐集、使用和揭露個人資料的關鍵要素。UID 2.0 正是一種 privacy self-management 的展現,藉由強調賦予使用者控制其個人資料的權利,來促進使用者的隱私。 35 | 36 | 但是,顯而易見地 privacy self-management 並不能有效地賦予個人對其資料的控制權,社會科學與心理學研究一再顯示了人們不一定可以有效地控制個人資料。我想多數人的生活經驗也會同意這點:幾乎沒人會真的把隱私權政策讀完,而且人們往往無法評估給出個人資料的代價為何。在 GDPR 出現之後,四處橫行的 cookie banner 不只沒有讓大家獲得更多隱私,還非常惱人,如今出現的 UID 2.0,大概也只會是下一個 cookie banner。當然也沒必要完全否定 UID 2.0,它的確(部分地)解決了如今基於 third-party cookie 的 cross-site tracking 通常沒獲得同意且隱私管理困難的問題,在這種意義上,仍然是一種進步。但是我不相信 privacy self-management 可以帶領我們通往更尊重隱私的網路世界。 37 | 38 | 更令人困擾的是,UID 2.0 的設計藉由一個中心化的服務以及通用的身分識別依據(email address),繞過了目前瀏覽器對於 cross-site tracking 的防禦機制,其他類似提案(例如 SWAN.community )使用類似 bounce tracker 的方式至少還有偵測與防禦方式,UID 2.0 幾乎只能回歸傳統 content blocking 來攔截相關的 HTTP requests,在瀏覽器隱私的角度來看,這正好顯示了目前如此繁瑣的防禦措施仍然可能被繞過。 39 | 40 | ## 結語 41 | UID 2.0 的未來會如何,目前還很難說。一方面,這個方案獲得了眾多廣告商與平台的支持,例如 Criteo、Neilson 等廣告產業的重要角色都表達支持,Prebig.org(Header Bidding 技術的提供者)也決定擔任 UID2 operator 的角色,但另一方面,Google 則明確表達拒絕任何個人化追蹤的技術,算是間接地拒絕了 UID 2.0 的提案。在廣告商眼中,這無疑又是個一整群小蝦米對抗 Google 大鯨魚的故事,但在在乎隱私的使用者眼中,他們的資料終究只是無奈地等著被吞食的藻類而已,至於是被小蝦米還是大鯨魚吞食,似乎也沒那麼重要了。 -------------------------------------------------------------------------------- /content/docs/privacy-preserving-web-tracking/topics.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 2 3 | title: "Topics API" 4 | --- 5 | # Topics API 6 | Google Topics API是一個用於追蹤使用者的瀏覽紀錄,藉此投放使用者可能感興趣的廣告。與 FLoC 一樣,Topics API 透過分析使用者的瀏覽紀錄來推測使用者感興趣的「主題」(所以叫做 Topics),並將其提供給廣告業者,以便投放使用者會感興趣的廣告。Topics API 的推出旨在解決 FLoC 所引發的隱私爭議,它不讓追蹤者自己蒐集數據以追蹤使用者(群)的喜好,而是直接在瀏覽器裡面推測出使用者喜好之後,將推測結果傳給廣告業者,藉此免除這些廣告業者自己部屬追蹤器所可能造成的損傷。 7 | 8 | ## Topics API 如何運作 9 | 10 | 首先,瀏覽器會為每個訪問過的網站分配一個主題,這個主題來自Google 預先整理出的四百多個可能主題列表1。例如,一個 cnn.com 的主題可能是「新聞」,而一個 amazon.com 的主題可能是「購物」。瀏覽器會內建一個很輕量的 ML classifier,這個模型會根據網站的域名來判斷網站的內容。例如 tennis.shop.example 可能會被歸類在「運動」類,但 shop.example/tennis 會因為 classifier 只能看到 shop.example 而被歸類在「電商」類。另外,這個分類是在客戶端完成的,所以其他人不會知道使用者造訪了哪些網站。 11 | 12 | 瀏覽器會將使用者的瀏覽紀錄轉換為一個主題的歷史紀錄。主題歷史紀錄是一個列表,其中包含了使用者瀏覽過的所有網站的主題,以及每個主題出現的次數。 13 | 14 | 接著,在每一個週期(epoch,目前定義是一週),瀏覽器會計算好五個最感興趣的主題給個別網站。每次網站呼叫 document.browsingTopics() 要求主題時,瀏覽器有 95% 的機率從五個最感興趣主題中選一個回傳,又有 5% 機率會隨機找一個主題回傳。接著在這個週期內,同個網站每次回傳的主題都會是一樣的,不同網站可能回傳不同的主題,但都是由那五個最感興趣主題中選出來的。無論是真的還是隨機的,這個主題會維持在主題列表中三週。 15 | 16 | 在下一個週期時,網站如果又請求了主題,瀏覽器會多給網站一個主題(一樣有 5% 是隨機選的),於是此時網站可以看到兩個不同主題。這個過程會一路重複到列表超過三個主題,也就是最早加入的主題已經存活達三週了,則會把最早的主題移除,以確保任何一個時間點網站能查到的主題至多只有三個。 17 | 18 | 以下表舉例(實際上主題列表並未排序,為講解方便故表中有排序): 19 | ![](/images/topics.png) 20 | 21 | 22 | 之所以會存在 5% 隨機選擇的主題,可以從兩個不同角度來講。首先,這個做可以確保每個主題都會有夠多人,不會有冷門到只有少數幾個人的主題。此外,這樣的設計還提供的可否認性:如果使用者被發現喜歡某個主題,他可以宣稱這是被隨機選的而非他真的喜歡,而且其他人不會知道這是不是真的隨機選出來的。另外,從五個主題隨機選以及 5% 隨機主題也確保了主題列表無法被用於 cross-site tracking。 23 | 24 | 值得注意的是,只有使用者明確造訪的網站會被納入主題計算(例如點擊連結或直接打網址),iframe 裡面的瀏覽不會被納入計算。此外,網站可以看到的主題是由 top-level domain 決定的,也就是 blog.example 如果包含的 tracker.example 的資源,這個來自 tracker.example 的資源看到的主題列表等同於 blog.example 的主題列表,而與 tracker.example 本身作為 top-level domain 時的主題列表不同。 25 | 26 | 此外,只有觀察到使用者在過去三週內訪問過的主題的追蹤器才能接收到該主題。例如,如果有個追蹤器過去三週從未出現在我瀏覽「新聞」類型的網站,則當這個追蹤器請求主題列表時,它不會收到「新聞」這個主題。這種過濾機制確保只有 tracker 實際觀察到的主題才會揭露給 tracker。為了混淆資訊,機率 5% 的隨機主題不受此過濾影響。 27 | 28 | ## 隱私分析 29 | Google 提出 Topics API 旨在解決針對 FLoC 的隱私批評,他們也對於 Topics API 提供了一些隱私分析。同時,Mozilla 與 Brave 的研究員們同時也對 Topics API 提出了一些疑慮。在此節我們一一來看這些分析內容。 30 | 31 | ### Topics API 用於 Cross-site Tracking 的可能性 32 | FLoC 的一大批評是它可能被用於 cross-site tracking,對此 Topics API 使用了三種機制來減少資訊洩漏,藉此增加 cross-site tracking 的難度。 33 | 34 | 首先,不同的網站在同一個週期內接收到的主題列表並不一定相同,所以網站A上的使用者主題列表通常不會剛好等同於網站B上的主題列表,所以不會形成 cross-site 的 identifier。 35 | 36 | 此外,因為主題列表每週只更新一次,所以要蒐集到足夠多資訊,會需要很久。 37 | 38 | 最後,因為存在 5% 的隨機主題,使主題列表有更大的隨機性,無法直接用於 identifier。即便攻擊者暗中紀錄過去出現過的所有主題,當趨近無限多個週期之後,所有使用者曾經出現過的主題都會是一樣的。 39 | 40 | 理論上,這些設計應該使得 Topics API 用於 cross-site tracking 並非易事,但這也不表示就毫無可能。更甚至可以說,如果攻擊者可以建構出一個移除雜訊的主題列表,則會有 cross-site 的特徵,雖然這個特徵可能沒有獨特到可以直接作為 identifier,但如果再搭配 IP address 以及 browser fingerprinting 等傳統特徵,則有可能建構出足夠獨特的 identifer。在後文我們會討論更多移除雜訊的可能性。 41 | 42 | ### Topics API 洩漏資訊量不會比 Third-party Cookie 多 43 | 對 FLoC 的一個重要批評是,如果 tracker 未曾出現在使用者 A 瀏覽的新聞網站,它可能因為使用者 B 常常瀏覽新聞網站,而且 A、B 兩人在同個 cohort 內,所以得知 A 也愛瀏覽新聞網站,這使得 FLoC 洩漏了 third-party cookie 本不會洩漏的資訊。為了避免類型的問題,Topics API 限制 tracker 獲得的主題列表中,不會出現它未曾得知使用者存取過的網站主題,以上例來說,因為 tracker 未曾學到使用者 A 瀏覽新聞網站,所以 Topics API 回傳主題列表時,不會讓 tracker 知道使用者 A 對新聞感興趣。 44 | 45 | 避免讓 Third Party 對使用者行為做分析 46 | 47 | 此外,不同於過去 third-party cookie 讓追蹤者可以看到所有使用者行為,並自己去分析使用者的喜好,因為 Topics API 只會揭露固定那些主題給廣告投放者,而且其主題判斷還只根據網域,不根據網站內容,所以 tracker 可以學到的資訊會被限縮在非常粗略的主題分類,而且很難推測網站內容。 48 | 49 | ### 具備充分的可解釋性與可控制性 50 | 相較於一堆 identifier,使用者可以輕易理解自己被推測喜歡的主題,為什麼被推測喜歡這些主題(畢竟就只是基於瀏覽紀錄的統計結果而已),並且刪除那些他們不想被投放廣告的主題。這使得使投放廣告的選擇變得可解釋,而且使用者也能控制資訊洩漏。另外,當使用者清除瀏覽紀錄時,主題列表也會清空,確保瀏覽資訊有被確實清理乾淨,不會意外洩漏。 51 | 52 | 這麼做的一大好處是,使用者如果認為特定主題太過敏感,他可以手動移除這些主題。而且 Google 在挑選主題時也已經先把多數人認為敏感的主題移除了,所以手動移除不恰當的主題不會太費心。不過這也意味著,相較於過去我們可以單方面地指責追蹤者不該任意蒐集使用者資料,現在使用者必須為保護自己隱私付出更大的心力,而我們又知道多數使用者並不會想去改預設設定,這樣的設計可能使得廣告商可以更正當地濫用這些資料,並且把責任推卸給使用者說是他們自己不改預設設定的。長遠來看,我不認為這必然地可以保護使用者隱私。 53 | 54 | ## 攻擊 55 | 56 | 若要使用 Topics API 作為追蹤工具,第一步便是要把那 5% 的隨機主題 {{< katex >}}t_{rnd}{{< /katex >}} 移除。一個很直觀的攻擊是,攻擊者可以透過每個主題出現的次數來作統計檢定,識別出隨機主題 {{< katex >}}t_{rnd}{{< /katex >}}。試想,當抽到 5% 給出隨機主題時,一個隨機主題出現的機率是 {{< katex >}}p_{rnd} = p / N_{topic}{{< /katex >}},當抽到 95% 給出正確主題時,一個主題出現的機率是 {{< katex >}}t_e{{< /katex >}}。因為是 {{< katex >}}N{{< /katex >}} 個週期隨機抽,每個週期抽一次,所以是個 {{< katex >}}N{{< /katex >}} 次的獨立伯努利試驗,我們可以直接算出一個主題在 {{< katex >}}N{{< /katex >}} 個週期之後出現在主題列表的超過 {{< katex >}}f_{rnd}{{< /katex >}} 次的機率: 57 | 58 | {{< katex display >}} 59 | p_{rnd} (f_{rnd}, N, p_{rnd}) = 1 - \sum_{k \in K} (N, k) p_{rnd}^k (1 - p_{rnd})^{N - k} 60 | {{< /katex >}} 61 | 62 | 63 | 因此我們可以去找到一個 threshold {{< katex >}}f_{min}{{< /katex >}} 使 {{< katex >}}p_{rnd}{{< /katex >}} 小於一個特定機率 {{< katex >}}p_{min}{{< /katex >}},這個機率類似於 p-value 的概念,表達該主題出現這麼多次的機率要低到什麼程度時,我們才會相信這個主題不是隨機抽的。如果有個主題 t,在 N 個週期內出現的次數小於 {{< katex >}}f_{min}{{< /katex >}},則我們可以猜測它是隨機的,直接將其排除。這麼做並不保證一定會還原出真正的主題列表,可能會排除一些相對罕見但真的主題,不過這也沒關係,正如我們在 browser fingerprinting 時討論過,特徵只需要是穩定且獨特即可,不需要是真的。 64 | 65 | 藉由這個方法,Jha 等人發現,在小規模的實驗環境中,可以很穩定地重新識別一部分的使用者(大約 15%),雖然效果肯定不如 third-party cookie 那麼強,但也顯示了 Topics API 並非完全不可能用於針對個人的 cross-site tracking。不過無疑的,Topics API 大幅改善了現有追蹤技術的缺點,在這點上仍然是個巨大的進步。 -------------------------------------------------------------------------------- /content/docs/stateful-tracking/content-blocking-circumvention.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 5 3 | title: "如何對付傳統追蹤技術 (II):如何繞過基於 Filter List 的 Content Blocking" 4 | --- 5 | 6 | # 如何對付傳統追蹤技術 (II):如何繞過基於 Filter List 的 Content Blocking 7 | 正如任何資安領域,只要有攻擊,就會有防禦,只要有防禦,就會有繞過。既然 content blocking 會阻擋 tracking script,也就有人會嘗試繞過。如同前面所提到的,content blocker 在識別 tracking script 與廣告的手段中,最主流的是使用黑名單。因此,如何繞過黑名單自然會是最受人矚目的問題。此文將整理數個技術上可行的辦法,有些很常見,有些則是可遇不可求甚至很奇葩的。 8 | 9 | 稍微複習一下。Filter list 可以分成兩種型態: 10 | - Resource blocking:封鎖整個 domain 或特定 URL path,讓 request 無法發出 11 | - Element hiding:隱藏或刪除特定的 DOM element 12 | 因為後者主要是做 ad blocking,對於防禦 tracker 沒什麼幫助,所以本文會著重在 resource blocking 的繞過。 13 | 14 | ## 常見的繞過方式 15 | ### 更改 URL paths 16 | 一個很直覺的方法是,既然 URL path 會被封鎖,那我就把 URL path 改掉,不就繞過 filter list 了?舉例來說,假設 `tracker.example/tracker.js` 被封鎖了,那就改成 `tracker.example/tracker2.js`,如果又被封鎖了,就繼續改。出乎意料的是,這方法超級有效。因為多數 filter list 是靠社群夥伴通報與維護,如果追蹤者更改 URL path,往往要等到有人發現並通報後,maintainer 才能將其加入 filter list。改檔案位置只需要花幾分鐘的時間,但 filter list maintainer 可能要好幾天甚至好幾個月後才會發現 filter list 被繞過了。 17 | 18 | 甚至,其實根本不用一直換檔名,這樣太麻煩了,直接讓任意 URL path 都會回應同一個檔案不就好了?例如,我們之前觀察到一個 domain,只要 path 是四個英文字母加上 .js(e.g. `tracker.example/abcd.js` 與 `tracker.example/efgh.js` ),都會回傳一模一樣的 script,如此則連換檔案都不用了,client side 直接隨機選一個 path 存取即可。或是我們也有觀察到那種明顯是隨機生成的 path,像是 `/i2dl71/921livp0m30yh8q/867vuq/768kypp.php`。 19 | 20 | 可能讀者會很疑惑,為什麼不直接封鎖該 domain 就好?許多 filter list 的 policy 要求盡可能避免 false positive,並認為 false positive 比 false negative 嚴重,寧可縱放不可誤殺。也許該 domain 上有些檔案不是 tracker,直接封鎖整個 domain 會造成誤殺。某種意義上,追蹤者大概就是在運用這個特性,才能濫用 URL paths 吧。 21 | 22 | ### 更改 Domains 23 | 既然可以改 URL paths,當然也可以改 domain。如果 filter list 把 `tracker.example` 整個封鎖了,就改成用 `tracker2.example`。如同 URL paths 的困境,filter list maintainer 往往要隔一段時間才能發現 tracking domain 被改掉了。 24 | 25 | 改 domain 的策略很多。第一種是移到 first-party subdomain,例如之前提過的 CNAME cloaking,其造成的挑戰是,原本只有 `tracker.example` 需要封鎖,現在有成千上萬個不同 first-party subdomain 需要封鎖。更多討論可以讀針對 [CNAME cloaking]({{< relref "/docs/stateful-tracking/cross-site-tracking-without-cookie#cname-cloaking" >}}) 的討論。第二種策略是洗一堆 CDN 的 domain,像是 `*.cloudfront.net` 之類的,然後不斷換不同 domain,反正 domain 是 CDN 的,filter list 總不可能把整個 cloudfront 都封鎖。第三種策略是改 subdomain,像是從 `tracker.blog.example` 改成 `tracker2.blog.example`,因為 domain 都是自己的,這麼做沒什麼成本,又因為 `blog.example` 是正常的東西,filter list 不能直接把該 domain 完全阻擋掉,只能繼續貓追老鼠。 26 | 27 | 最後一種策略,也是最奇葩的策略,是用 domain generation algorithms (DGA) 去大量生產 domain,一個被封鎖就改用下一個,這整個過程完全是自動化的,filter list maintainer 只能很辛苦地在後面追趕,把剛出現的 tracking domain 加進 filter list,然後馬上就出現下一個新的 tracking domain,永無止盡。其甚至有個專有名詞稱為 *revolving domain*:會一直轉的 domain。舉例來說,有個廣告商叫做 LuckyAds,他有各種亂數生產的 domain,像是 `qakdki.com` 與 `inpiza.com`,這些都是用於繞過 filter list。 28 | 29 | 當然 filter list maintainer 也不會就此放棄。目前 EasyList(最常見的 filter list)有兩種策略回應。首先,他們有個 bot 去自動監測那些已知在使用 revolving domain 的網站,如果看到新的 domain 就自動封鎖。此外,對於那些已知在使用 revolving domain 的網站,預設阻擋所有 third-party requests,如此雖然可以阻擋 revolving domain,但可能讓網站功能壞掉。 30 | 31 | ## 罕見的繞過方式 32 | ### 利用沒寫好的例外規則 33 | 例外規則,顧名思義,就是指有些 request 就算符合 filter list 裡面的東西,也會因為它是例外而被放行。通常這用於一些誤判(例如多數 `ads.js` 都是廣告,但偏偏有些不是),或是用於繞過 anti-ad blocking(到後面講 ad blocking 如何用於 fingerprinting 時可能會討論到)。 34 | 35 | 可是,filter list 是人寫的,終究會出錯。於是有人就想到,如果利用沒寫好的例外規則來繞過 filter list,聽起來好像可行。 36 | 37 | 舉例來說,曾經 EasyList 有一條例外規則是: 38 | ``` 39 | @@||redtube.com*/adframe.js 40 | ``` 41 | 42 | 其意思是,domain name 部份的開頭是 `redtube.com`,且 URL 的結尾是 `/adframe.js`。 43 | 這... 聽起來怪怪的吧?於是就有人想到這樣繞過: 44 | ``` 45 | http://redtube.com.umamdmo.com/.......?q=/adframe.js 46 | ``` 47 | 這符合開頭是 `redtube.com` 結尾是 `/adframe.js`,但這顯然不是一開始想加入白名單的東西吧。 48 | 49 | 有趣的是,這個案例存活了兩年多才被發現。當然,這種繞過方式是可遇不可求的。 50 | 51 | ### 把 Tracker 移動到 Inline Script 52 | 一直以來,大家都習慣把 tracker 的 Javascript 放在獨立一個檔案,然後用 ``),是不是就沒辦法封鎖了?於是就有一些追蹤者鼓勵網站管理員把 tracker 放到 inline script,藉此避免被封鎖。EasyList 的回應是,利用 [CSP policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 去 block 這些不想要被執行的 inline script。 55 | 56 | ## 其他 57 | ### WebSocket(曾經) 58 | 曾經有很長一段時間,Chrome extension 沒有辦法攔截 WebSocket 的流量,於是有些廣告商與追蹤者就開始用 WebSocket 來傳送廣告與 tracker。不過這個 bug 在 2017 年修掉之後,基本上就沒用了。 59 | 60 | ### Anti-ad blocker 61 | 應該很多人看過有些網站會要求關閉 ad blocker,否則就不允許使用。基本上這不是在繞過 filter list,而是故意讓網站會因為 content blocker 而壞掉,逼迫使用者關閉 content blocker。不過現在也有針對 anti-ad blocker 的 filter list,專門把這些 script 砍掉。 62 | 63 | 64 | ## 參考資料 65 | Mshabab Alrizah, Sencun Zhu, Xinyu Xing, and Gang Wang. 2019. Errors, Misunderstandings, and Attacks: Analyzing the Crowdsourcing Process of Ad-blocking Systems. In Proceedings of the Internet Measurement Conference (IMC '19). Association for Computing Machinery, New York, NY, USA, 230–244. https://doi.org/10.1145/3355369.3355588 66 | Su-Chin Lin, Kai-Hsiang Chou, Yen Chen, Hsu-Chun Hsiao, Darion Cassel, Lujo Bauer, and Limin Jia. 2022. Investigating Advertisers’ Domain-changing Behaviors and Their Impacts on Ad-blocker Filter Lists. In Proceedings of the ACM Web Conference 2022 (WWW '22). Association for Computing Machinery, New York, NY, USA, 576–587. https://doi.org/10.1145/3485447.3512218 -------------------------------------------------------------------------------- /content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | title: "Web Tracking 筆記" 4 | --- 5 | 6 | # Web Tracking 筆記 7 | 這一系列的文章將會介紹有關 web tracking 各種你不需要的、對你人生毫無幫助的冷知識。我們將從最基礎的概念開始講起,從追蹤技巧講到反追蹤技巧,從基本技術講到奇技淫巧,然後再討論到新世代的追蹤技術長怎樣。 8 | 9 | 如果你喜歡這一系列的文章,可以考慮到 GitHub 上幫按個 [star](https://github.com/s3131212/web-tracking-notes),這對我來說是很大的鼓勵! 10 | 11 | ## 起源 12 | 這系列文章最早只是我想整理自己在 web tracking 領域的所學與一些個人見解。因為整理時正逢 iThome 鐵人賽,就順便參賽了。當時參賽標題是[〈天羅地網:淺談 Web Tracking 的過去、現在、未來〉](https://ithelp.ithome.com.tw/users/20152544/ironman/5770)。之後覺得當時有些文章寫得不理想,有些想要做延伸,所以就把文章獨立出來了。在我寫下這段文字的當下,文章內容跟鐵人賽時的文章基本上是一樣的,但如果之後要更新,我會以這邊為優先。 13 | 14 | 也因為這系列文章起源於個人筆記,所以**文章內容可能會有誤或是缺少重要資訊**,我已經盡可能做好事實查核,但可能還是會有疏忽,也可能是單純我誤解了。如果有看到錯誤,還請大家多指教。另外,也因為一開始出自鐵人賽,文風會變得有點像是部落格文章,我還正在陸續修用字。 15 | 16 | ## 什麼是 Web Tracking 17 | Web tracking 是指「不同情境中重新識別使用者,而且技術上無需使用者的期待或同意」。其中,使用「情境」指使用者期望與其他類似情境分開的一系列活動,例如時隔多日再次造訪同個網站,或是同時造訪兩個不同網站,都可能屬於不同情境。強調技術上無須期待或同意,用於排除符合預期的重新辨識(例如登入帳號),並且強調這是技術上而言,法律上或道德上可能仍然需要取得同意。稍微更技術面向地來講,web tracking 是「藉由為每個使用者指定或計算出一個(唯一的) identifier,在未來遇到同一個使用者時,以此辨認出這是哪個使用者」的一系列的技術。 18 | 19 | 舉例來說,我是一個廣告商,使用者在 blog.example 瀏覽了一台筆電的開箱文,於是我就指定這個使用者的識別碼是 userA,並猜想他對這台筆電有興趣。當使用者又在 blog.example 繼續瀏覽了一個耳機的開箱文,我又記錄下來他喜歡耳機。至此都還在同個網站上追蹤使用者,即 same-site tracking。當使用者瀏覽 news.example 時,藉由 web tracking 技術,我發現他就是 userA,正是之前在瀏覽筆電開箱文的那個人,於是就可以在 news.example 投放廣告吸引他購買這台筆電,這就是 cross-site tracking。 20 | 21 | Cross-site tracking 的危害通常被認為大於 same-site tracking 的危害。讓網站管理員知道使用者瀏覽他的網站的哪些文章可能不是大問題,尤其如果使用者有登入帳號時,網站管理員肯定知道他是誰、他在看哪些頁面。不過,如果無論使用者在瀏覽哪個網站,Facebook 都可以看到他的瀏覽紀錄,那大概就是個大問題了。也因此,許多 web tracking 的攻擊與防禦手段都更著重於 cross-site tracking,或有時會說 first-party site 是隱私界線。當然這不是說 same-site tracking 不重要,而是因其性質差異而受到比較少關注而已。 22 | 23 | 早期的 web tracking 通常是嘗試把一個唯一的 identifier 儲存在使用者的瀏覽器中,以後只要檢視該 identifier 便可以知道使用者是誰。通常這種需要在瀏覽器中儲存資料的技術被稱為 stateful tracking。但 stateful tracking 有個問題:如果使用者把 identifier 刪掉,就追蹤不到他了。於是之後有人在想,如果不將 identifier 儲存於瀏覽器中,而是利用一些特徵去「算出」identifier,使用者就沒辦法把儲存的資料刪掉了,至此我們進入 stateless tracking 的時代。在 stateless tracking 的技術中,最知名也最廣泛被使用的是 browser fingerprinting。Browser fingerprinting 利用了瀏覽器、作業系統、硬體等的各種資訊,去算出一個 identifier,因為這些資訊不一定會常有變動,所以每次算出來的 identifier 的課都有很高的機率是一致或雷同的。 24 | 25 | ## 所以這跟 Security 有什麼關係? 26 | 一般來說,資安(security)與隱私(privacy)是難以分割的,例如藉由資安的技術保護隱私(個人資料等),隱私的洩漏可能創造新的資安危機,所以我們常把 security 與 privacy 合併使用。我認為不應該把 security 狹隘地想像成駭進別人的電腦然後提權拿到 root,其他重要的問題,像是如何維持通訊安全(e.g. HTTPS)以及在網路上保持匿名(e.g. Tor),或是在資訊不洩漏的情況下做到很多功能(e.g. privacy-preserving 的應用、密碼學黑魔法),也都是資安的一環。 27 | 28 | 也許 web tracking 不像是其他典型資安領域的 attack & defense,但從前述對於 web tracking 用途的論述可以看出,這些技術可能造成隱私危害,讓使用者在網路上無所遁形,而這樣的隱私洩漏可以是種資安威脅。希望我有成功說服大家。 29 | 30 | 如果很疑惑這些隱私洩漏有什麼關係,或甚至覺得隱私不重要,我不認為自己能用三言兩語說服你,也不覺得說服大家隱私很重要是這系列文章的目的,但我很推薦 Shoshana Zuboff 的《監控資本主義時代》,這本書做了許多論述。 31 | 32 | ## 我為什麼需要知道 Web Tracking 相關技術? 33 | 同學,你問錯問題了。比較好的問題是:我需要知道 web tracking 相關技術嗎?為什麼? 34 | 35 | 很遺憾的,你不需要知道 web tracking 相關技術。除非這是你在研究 browser privacy 或是你就在網路分析公司工作,否則這些知識本身對你的人生大概不會有什麼幫助。 36 | 37 | 不過我想來分享一下自己的學習心得。Web tracking 技術最有趣的點在於發現一些看似正常的設計竟然可以被如此運用,看似完備的防禦可以被如此繞過,那種「還可以這樣搞喔天啊」的驚奇感也就油然而生。此外,在研究這些技術的過程中,我也開始會去注意每一個功能的設計有沒有可能 leak 一些意想不到的資訊,這些資訊可能被怎麼運用。 38 | 39 | 當然,這只是我非常個人的收穫,讀者可能會有不一樣的收穫,可能學到更多東西,可能找到新的興趣,可能會覺得奇怪的知識增加了,也可能讀完之後只覺得自己浪費時間讀一些不知道能做什麼的東西。每個人的學習經驗與歷程都是不一樣的,但也正是如此,世界才如此有趣吧。 40 | 41 | 42 | ## 系列文章結構 43 | 希望我有成功說服讀者嘗試把這系列的文章看下去,至此終於可以來介紹這一系列的文章會寫什麼了。 44 | 45 | 除了開頭與結尾以外,系列文章大概可以分成三大部份: 46 | - Stateful Tracking:介紹一些早期與當代的 stateful tracking,以及這些 stateful tracking 可以如何被避免,而這些阻擋 stateful tracking 的手段又如何被繞過與攻擊。 47 | - Browser Fingerprinting:介紹各種 browser fingerprinting 的技術以及其反制方法,其中會再分成常見的、廣泛被使用的方法,以及特殊的甚至奇葩的方法。 48 | - Privacy-preserving Web Tracking:如果我們一方面想要做 tracking,但同時又想保護使用者的隱私,我們可以怎麼做?目前有許多解法,但還沒有收斂到一個大家共通的標準,也可能以後都不會收斂。我會挑選幾個值得討論的例子來講。 49 | 50 | 每一個部份會寫幾篇文章,會舉哪些例子,雖然心中大概有個圖像了,但還沒有很肯定,所以就先不放出來了。等系列文章寫完之後再回頭修改這段文字吧。 51 | 52 | 原則上每篇文章之間連貫性不高,如果真有連貫性,我也會盡可能地明顯讓讀者知道這篇文章需要前面哪些文章作為基底,所以可以跳著讀,應該不會有太大的問題。 53 | 54 | ## 預設背景知識 55 | 很遺憾的(?),閱讀這系列的文章會需要一些基本知識。 56 | 57 | 在一開始介紹 stateful tracking 與 browser fingerprinting 時,會需要 HTTP 與 JavaScript 的基礎,基本上只要有寫過網頁前端或是打過 web 類型的 CTF 題目就非常夠用了。文章中提到的多數技術,都會在第一次提及時做 high-level 的介紹,但這只是幫已經對該技術有基礎認識的讀者回憶起那是什麼,完全沒聽過該技術的讀者很可能聽不懂我的介紹。如果真的缺乏什麼背景知識,網路上也都有相關資源可以閱讀(尤其推薦 [MDN](https://developer.mozilla.org/)),不用擔心會需要花費很大心力才能跟上。有鑑於這系列文章的定位是會基本的 web 就要能看懂,如果讀者需要花費很大心力跟上進度,那大概就是我寫得不夠淺顯易懂吧,還請讓我知道哪裡可以改進,謝謝。 58 | 59 | 後面會介紹 privacy-preserving web tracking,其中有些技術可能會應用到專業領域的知識,像是密碼學或是 Machine Learning。兩個領域也都有許多網路上的資源可以參考,但可能會稍微難一些。 60 | 61 | ## 自我介紹 62 | 突然想到還沒介紹自己是誰呢。不過我知道沒人在乎我是誰。 63 | 64 | 我是 [Allen Chou](https://allenchou.cc)。好,沒了。 65 | 66 | ## 授權 67 | 本系列文章以 [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) 釋出。如果要使用這個網站上的資訊,基本上只要有 credit 我都不會有問題。如果對使用授權有任何疑義,歡迎聯繫我。 68 | 69 | ## 貢獻 70 | 假設你發現網站上有任何資訊有誤,覺得哪裡可以寫得更好,或是有些感覺也可以分享的內容,都可以到 [repo](https://github.com/s3131212/web-tracking-notes) 開個 issue 或 PR。 71 | 72 | ## 一點結語 73 | 在一篇楔子裡面寫結語,是個很有趣的體驗呢。 74 | 75 | 我想提醒讀者,技術不是中立的,我也不是中立的,我更沒有打算假裝自己很中立。我的立場很鮮明:web tracking 會對隱私造成傷害,這些技術是有害的,我們要反制它們。在後面文章的論述,我對於 web tracking 的論述也是以他們有害為論述核心。期許讀者在閱讀的同時,小心地注意文章中明示或暗示的價值,並且時刻反思我講的真的有道理,不要輕易被我帶風向了。 76 | 77 | 此外,我不是相關領域的專家,也就只是讀了幾篇文章而已,對這個領域的認識淺薄到實在不足掛齒,更遑論拿來說嘴,寫這系列文章也只是想整理過去自己所學。如果我有寫錯,還希望各位先進能不吝指教。然後這系列的文章會有一點晶晶體,我嘗試過盡可能講中文,但有些名詞翻譯成中文感覺就是不對,所以就覺得算了就這樣吧,畢竟 web tracking 真的很 amazing,翻成 Chinese 整個 meaning 就偏掉了。 78 | 79 | 如果有任何想法想要交流,都可以到 repo 開 issue,或是寄信給我(s3131212 at gmail dot com)。 -------------------------------------------------------------------------------- /content/docs/privacy-preserving-web-tracking/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 5 3 | title: "Privacy-preserving Web Tracking" 4 | --- 5 | 6 | # Web Tracking 可以是 Privacy-preserving 的嗎? 7 | 接著,我們要進入最後一個篇章。在討論過 web tracking 的各種技術,並見識到躲避追蹤有多困難之後,我們不禁要問:難道就沒有兼顧隱私的 web tracking 嗎?為什麼我們不得不在許多 Web 的美好功能以及隱私之間擇一呢?所以接下來我們就要來檢視這個問題了。 8 | 9 | ## 何謂 Privacy-preserving 10 | Privacy-preserving 並沒有一個嚴謹的定義,而是視情況有不同定義。舉例來說:同樣是「Alice 點了 A 廣告然後也點了 B 廣告」,最簡單的定義可能是只要不知道是 Alice,只知道「有個使用者點了 A 廣告然後也點了 B 廣告」;稍微更強烈一點,可能是「有個使用者點了 A 廣告和 B 廣告,但不知道順序」,也可以是「有個使用者點了 A 廣告,他可能也點了 B 廣告,但我不確定」;更進一步,也可以是「這 10 個人都點了 A,那 8 個人都點了 B,其中最多有 4 人兩者都點了,至於 Alice 在哪,我不知道」,甚至「這 10±3 個人都點了 A,那 8±3 個人都點了 B,其中有 2±2 人兩者都點了,至於具體數字是多少、Alice 在哪,我不知道」;如果有多個人參與資料蒐集,還可以是「我知道 Alice 可能點了 A,你知道 Alice 可能點了 B」,這些都可以是 privacy-preserving 的定義。至此我們也可以看出,其定義取決於我們到底想達成什麼目標、有多大的野心、想保護資料到什麼程度。因此在接下來的文章,可以特別注意這些方法是如何定義何謂隱私資料,又提供怎麼樣的保護。 11 | 12 | 此外,隱私保護也不是越多越好。最極致的隱私保護就是一點資訊都沒有:「我不知道 Alice 是誰,不知道哪個使用者點了什麼廣告,我什麼都不知道」,但這個資料一點幫助都沒有啊。因此,我們必須在隱私(privacy)與有用(usefulness)之間取得一個平衡。 13 | 14 | 15 | ## Privacy-preserving Web Tracking? 16 | Tracking 和 privacy-preserving 聽起來就是衝突的,怎麼可能有 privacy-preserving web tracking?某種意義上來說的確是如此,所以這個名字的確有些誤導。我們想問的問題其實是:對於那些原本透過 web tracking 達成的目標,有可能用 privacy-preserving 的方法達成一樣目標嗎?這些方法可能符合 web tracking 的定義(還是有 identifier 並透過它來連結使用者不同時間的操作),也可能完全跳脫傳統 web tracking 的框架。接下來所介紹的方法,多數都屬於前者,也就只不過是稍微更保護隱私一點的 web tracking 方法,但有些其實算是後者,它本質上不再是種 web tracking 了。 17 | 18 | 細心的讀者可能已經發現了,此章和過去兩章所著重的面向其實很不一樣。過去我們花了很多篇幅講各種 web tracking 的技術,而不討論 web tracking 本身到底是為了什麼,只有在第一章快速帶過。或是更白話來說,當我們在一開始說了「廣告商透過具隱私侵害性的 web tracking 技術追蹤使用者的廣告點擊」後,就一直討論所以何謂具隱私侵害性的 web tracking 技術,又可以如何反制這些技術,卻不討論所以廣告商拿到這些資料之後他到底能幹麻。接下來我們要討論的正是,廣告商拿到這些資料之後想要做什麼,有沒有可能其實不使用一些具隱私侵害性的技術也可以達到一樣的效果。 19 | 20 | ## 有哪些問題需要解決? 21 | Web tracking 目前於廣告產業最主要的運用是廣告的個人化投放與聯盟行銷,也就是需要知道使用者喜歡看怎樣的內容,並偵測他們點了哪些廣告,到了目標位置之後又做了哪些操作。在 privacy-preserving web tracking 中,我們同樣想達成這些目標。個人化廣告投放的技術包含 Google 的 FLoC 與 Topics API 等,追蹤使用者的廣告點擊,則有 Mozilla 與 Meta 的 Interoperable Private Attribution 、Apple 的 Private Click Measurement、Google 的 Attribution Reporting API 等。其他 web tracking 的運用還包含 web analytic 等,這些也都有相關的 privacy-preserving 的技術,但因為篇幅因素,可能無法介紹到他們了。 22 | 23 | ## 為什麼需要 Privacy-preserving Web Tracking 24 | 可能有些讀者會很疑惑,為什麼我們需要 privacy-preserving web tracking?何不直接消滅 web tracking?我認為 web tracking 的許多目的本身其實是合理的。例如,我們想要有免費的服務,總得有人來幫我們付錢,這時廣告商為我們付錢的同時想要最大化廣告投遞效益並追蹤成效,是個合理的要求。問題不在於他們想最佳化投遞並追蹤成效,而在於他們達成這些目的所使用的手段對使用者的隱私侵害過於巨大。也許我們會期待網路世界可以有個新的商業模式而不是完全靠廣告產業撐起,也許我們希望可以完全終結監控資本主義。然而,在那之前,我們需要有方式與廣告產業共存,privacy-preserving web tracking 便是要這樣的目標。我們不必也可能無法消滅 web tracking,但至少我們可以使 web tracking 的目標得以達成的同時保護自身隱私,更甚至,在達成隱私保護的前提下,仍能享受 web tracking 所能帶來的益處。 25 | 26 | ## 瀏覽器的隱私革命 27 | 至此應該有個問題,看起來只有一群在乎隱私的倡議者以及很想發論文的學者會對這種東西感興趣吧。的確,最早對於 privacy-preserving web tracking 的討論幾乎都來自學界,甚至早在二十多年前便有相關嘗試,這些發展雖然沒有直接影響瀏覽器的設計,但也為未來的隱私保護技術發展埋下種子與準備好養分。如今這些種子總算開始發芽了。大概從 2018 年左右開始,越來越多主流瀏覽器也開始導入各式各樣的 privacy-preserving web tracking,其中最浩大的(也最有爭議性的)嘗試便是 Google 的 Privacy Sandbox。所以發生什麼事了?一言以蔽之,大家都在嘗試為了 third-party cookie 被消滅後的 web tracking 世界鋪路。 28 | 29 | 有很長一段時間,網路和Web的設計從未考慮過安全或隱私的問題。但這十多年(或更多)來,安全性的重視已經有顯著的提升,越來越多產品設計是 security by design,在設計初期便把安全性納入考量。隱私在這幾年也開始受到關注,雖然整體發展仍然落後於安全,但距離正在縮小。更重要的是,改善 Web 隱私的進程有 Google、Apple 等大公司的積極參與,這些大公司無疑地在科技發展上佔有舉足輕重的地位,尤其這些公司自己就是 Web 產業最大的利益關係人,無論是依賴廣告產業或是自己有開發瀏覽器,這使得這幾年來的隱私發展得以付諸實現,與過去單純的理論研究有著天壤之別。 30 | 31 | 在改善瀏覽器隱私的路途中,最大的魔王無疑是 third-party cookie。基本上整個瀏覽器的隱私革命,最大的戰場就是如何封鎖 third-party cookie 的同時不破壞現有生態系。其他戰場還有如何防制各種不同型態的 web tracking,例如幾乎所有瀏覽器都有自己的 bounce tracker 防禦方法,無論已經付諸實現或還是草案。 32 | 33 | 以 Google 與 Apple 兩家科技公司為例。多年來 Apple 一直提出新的Web API 草案,例如評估廣告成效的 Private Click Measurement 或是控制未加區隔的儲存空間存取的 Storage Access API,Apple 也有提出 Intelligent Tracking Prevention 等防範 web tracking 的機制,藉此限制資料流動與隱私洩漏。Google 的 Privacy Sandbox 則更宏偉且加引人注目,它幾乎根本改變了 Web 的運作,以全新的方法限制與改變資訊流動與利用的方式。Privacy Sandbox 整個計畫從 partitioned cookie(之前提過的 CHIPS)、追蹤使用者興趣(Topics API 與 FLoC)、衡量數位廣告成效(Attribution Reporting API)、詐欺防制(Private State Tokens API)、browser fingerprinting 防護(之前提過的 Privacy Budget)都有所涉及。然而它也伴隨著諸多爭議,並且已經推持數次,目前預計在 2024 年實施。 34 | 35 | 這場由瀏覽器公司帶頭的隱私革命,也有幾個有趣的特徵。 36 | 37 | 首先,同樣一個功能往往有多個同實在臺面上的技術,背後很可能又都是不同公司。舉例來說,同樣是衡量數位廣告成效,有 Google 的 Attribution Reporting API、Apple 的 Private Click Measurement、Meta 與 Mozilla 的 Interoperable Private Attribution,又很顯然的 Google 開發的 Chrome 只支援 Attribution Reporting API,Apple 開發的 Safari 只支援 Private Click Measurement,至於 Interoperable Private Attribution,目前還在草案,但短期內大概也只有 Mozilla 開發的 Firefox 有機會實作。每個公司有各自的技術,使得 privacy-preserving web tracking 基本上是被割據的狀態,又由幾乎處於壟斷地位的 Google 佔上風。 38 | 39 | 但這也不代表 Google 可以推動任意的變革。例如在2020年,Google取消了PIGIN提案,並推出了Turtledove 取代之,之後 Google 又在 2022 年撤回備受爭議的 FLoC,以 Topics API 取代之,同年 Google 也撤回了SameParty cookies。這些技術提案的變化反映出隱私議題現在已經成為 Web 生態討論不可忽視的一環,而且網路標準的討論也不再是只屬於傳統的那些大型科技公司,還有眾多利益關係人參與其中。這也意味著我們很難預料接下來 privacy-preserving web tracking 的世界會長怎樣。 40 | 41 | 與此同時,傳統的資料監管機構似乎已經不再主導這些關於隱私保護技術的辯論,而是讓市場上的各種利益關係人以及來自學界的聲音做討論。不過雖然資料監管機構並未介入,反壟斷調查倒是很關心 Google 的 Privacy Sandbox,畢竟 Chrome 是擁有最高市佔率的公司,任何風吹草動都足以影響整個產業。另外,目前的隱私法規應該如何跟上這場瀏覽器的隱私革命,例如 cookie 是否還有過去所想像的這麼有害,如何面對新穎 web tracking 方式的崛起等,也是個尚待解決的問題。 42 | 43 | 總結來說,過去十多年來在隱私保護界的諸多討論與技術,終於在這場 2018 年左右開花結果了,各大瀏覽器開始開發眾多新功能與重新設計,以更進一步保護使用者的隱私。然而如今可能已經有不下 20 個草案被提出,最終哪些會獲勝,得到市場青睞,則還不得而知。更重要也更令人憂心的是,我們還不知道這場革命最後會真的創造一個充分保護隱私的網路生態,或是成為再一個無疾而終的行銷宣傳。 44 | 45 | 在接下來的章節,我將介紹數個有趣的、值得一提的 privacy-preserving web tracking,有不少已經實作並被使用,也有一些還在草案階段。也必須提醒讀者,如同前面一再提到,這個領域的發展實在太過快速,也許當讀者翻開這本書時,有些資訊已經過時了,故請務必再次查證。 -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/webgl-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 4 3 | title: "WebGL Fingerprinting" 4 | --- 5 | 6 | # WebGL Fingerprinting 7 | WebGL Fingerprinting 分成兩個部份:WebGL Report Fingerprinting 與 WebGL Canvas Fingerpinting,後者基本上就是 canvas fingerprinting 的 WebGL 版本,所以才說他們是近親。WebGL Fingerprinting 利用了每台電腦在軟硬體上的不同,尤其是顯卡與顯示驅動不同,來做 fingerprinting。有趣的是,因為同一台電腦配備的顯卡一樣,所以 WebGL Fingerprinting 有可能用於跨瀏覽器的 tracking,不過這部份的討論會放到其他專文。 8 | 9 | ## 何謂 WebGL 10 | 一言以蔽之,WebGL 是 OpenGL 的 Web 版本。WebGL 提供了一個介面(interface)讓開發者可以藉由 JavaScript 操作 OpenGL ES 的 API,因此得以借助此 API 在瀏覽器中實現 2D 與 3D 的繪圖,並輸出至 ``,其中不需要使用瀏覽器外掛。WebGL 的程式大概包含幾個部份:JavaScript 控制主要邏輯,GLSL(OpenGL Shading Language)處理著色器的邏輯,然後 GPU 執行著色器的程式碼。 11 | 12 | ## WebGL Report Fingerprinting 13 | WebGL 可以讀取許多關於顯卡以及驅動的參數,通常我們稱呼這些資訊為 WebGL report,其用於了解該設備是否符合需求。參數的種類很多,可以到 [WebGL Report](https://webglreport.com/?v=2) 看到,這些資訊都可以用於 browser fingerprinting。 14 | 15 | ![](/images/webgl-report.png) 16 | 17 | 取得 WebGL 的參數通常可以使用 `WebGLRenderingContext` 的 `gl.getExtension` 與 `gl.getgetParameter`。舉例來說,在 WebGL 裡面,可以直接拿到顯卡的 vendor 與 renderer。其中可以分為一般的跟 unmasked 的,後者需要存取 debug 資料(`WEBGL_debug_renderer_info`),所以不一定每個 browser 都有。 18 | ```javascript 19 | const canvas = document.getElementById('canvas'); 20 | const gl = canvas.getContext('webgl'); 21 | const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); 22 | 23 | console.log(`${gl.getParameter(gl.VENDOR)}, ${gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)}`); 24 | console.log(`${gl.getParameter(gl.RENDERER)}, ${gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)}`); 25 | ``` 26 | 27 | 其他可以用來 fingerprint 的資料還有 WebGL [啟用了哪些 extension](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getSupportedExtensions)(可以用 `gl.getSupportedExtensions` 取得)以及[浮點數運算的 precision](https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat)(可以用 `gl.getShaderPrecisionFormat` 取得)等。 28 | 29 | 因為這不是 WebGL 教學文件,我也不懂 Computer Graphics,所以就不細談每個參數的意思,只需要知道這些參數可以用於 fingerprinting。關於實作,可以參考上述 WebGL Report 網站的 [repo](https://github.com/CesiumGS/webglreport)。 30 | 31 | ## WebGL Canvas Fingerprinting 32 | WebGL canvas fingerprinting 與 canvas fingerprinting 雷同,都是因為軟硬體上的不同,導致瀏覽器使用 WebGL 時可能繪製出不太一樣的圖,利用這個特性,追蹤者便可以用 WebGL 繪圖功能達成 browser fingerprinting。WebGL canvas fingerprinting 在實際使用上也與一般的 canvas fingerprinting 一樣,追蹤者需要繪製一張足夠複雜的圖,並把繪製結果匯出並使用雜湊函數產生 fingerprint。 33 | 34 | ### 為什麼 WebGL 的 render 結果不同 35 | 於是我們又回到一樣的問題:為什麼一段相同的程式碼在不同設備上會繪製出不同的結果。Wu 等人提出了一個有趣的解釋:繪製的差異來自於浮點數運算的差異。幾乎所有 Computer Graphics 的演算法都大量地使用浮點數,當不同平台在浮點數的精準度或是處理上有差異時,便會造成誤差,又因為浮點數運算實在太多了,這些誤差會被不斷放大直到可以被偵測出來。 36 | 37 | 在那之前,我們必須先理解 WebGL 的繪製流程。WebGL 的繪製流程可以被區分為三個步驟。首先,vertex shader 會執行一系列與頂點相關的操作,例如套用旋轉矩陣等。具體而言,vertex shader 會將 attribute(套用到個別頂點的屬性)與 uniform(套用到所有頂點的屬性)綁定到每個頂點(例如套用 texture 便是在這一步),並輸出轉換後 gl_Position 與 varyings。接著 vertex shader 的輸出會被送入 WebGL 的 rasterization 與varying interpolation 模組,將模型轉換成像素網格,並且利用頂點的 attributes 對 varyings 做內插(interpolation)。上述的輸出會被送入 fragment shader 做上色,其在套用各種相關操作(例如 texture mapping)之後,將結果繪製在 canvas 上。 38 | 39 | 在這整個過程中,涉及許多浮點數操作。在第一階段都有大量的矩陣操作。舉例來說,在處理頂點時,需要將頂點對應到像素網格時。WebGL 的座標系使用 [-1, 1] 的浮點數。如果有個點在 (0.0, 0.0),若要繪製到一張 8x8 的 canvas 上,並沒有一個整數點可以剛好對應到正中央,於是放在 (4, 4), (4, 5), (5,4), (5, 5) 都是對的。對於使用無條件捨去、無條件進入、四捨五入的系統,算出來的結果便會有些微的、肉眼不可見但可用於 fingerprinting 的差異。 40 | 41 | 在第二階段同樣會運用到許多浮點數。例如,在判斷一個像素點是否在圖形內、計算 z-buffer(識別頂點的前後順序)、對 varyings 做內插時,都會用到浮點數。例如在給定一個三角形,問一個像素點是否在三角形內,如果有便繪製三角形的顏色,否則使用底色。在不同實作下,可能會因為浮點數誤差而導致不同結果,導致該像素在不同實作下有不同的顏色。 42 | 43 | 在第三階段,處理上色時,一樣會遇到浮點數的問題。在 Web 上會使用 hex triplet(三個 8bit 的數字)來表示顏色,但在 WebGL 中,使用三個 0~1 的浮點數來表達。於是當 WebGL 要繪製畫面時,需要把 RGB 中 [0, 1] 的浮點數轉換成 [0, 255] 的 hex triplet。一個常見的實作方法是把 WebGL 的浮點數 256 後取近似的整數。假設在 A 瀏覽器在取近似值時使用無條件捨去,B 瀏覽器使用四捨五入,便會造成誤差。同樣是 (0.5, 0.5, 0.5),會被分別轉換成 (127, 127, 127) 與 (128, 128, 128)。在視覺上這幾乎完全沒差,但已經足以造成可用於 fingerprinting 的差異。 44 | 45 | ## 解法 46 | WebGL Report Fingerprinting 的解法,除了完整關掉 WebGL,便是投遞假的數值。例如 Brave 現在會將一些資訊混淆,extension 只回傳 `WEBGL_debug_renderer_info`,renderer 在同個 eTLD+1 同個 session 下回傳同樣的隨機字串。 47 | 48 | WebGL Canvas Fingerprinting 的防禦方法 canvas fingerprinting 很像,畢竟都是基於 canvas。基本上防禦 canvas fingerprinting 對 WebGL canvas fingerprinting 都還是有用。Tor Browser 一如往常的直接封鎖 WebGL。另一個有趣的方向是,既然我們知道 WebGL 的繪製差異來自浮點數,這意味著只要浮點數運算可以統一,便能完全消除繪製結果的差異。這正是前面提過的 Wu 等人的團隊之前做過的事情,他們把第一階段的 vertex rendering 移動到 JavaScript 來做,以確保行為統一;第二階段的 rasterization 與varying interpolation,以及第三階段的 fragment rendering,則改用整數去模擬有理數運算。根據他們的實驗,如此可以確保繪製結果一定一樣。 49 | 50 | ## 同場加映:在 browser 上面做 anti-VM 51 | 在惡意程式分析的領域中,攻擊者為了避免自己的惡意程式被分析,會偵測自己是不是在 VM 中,如果在 VM 中,就判定這是分析者的實驗環境,所以不要發作,讓分析者找不到惡意程式的行為,畢竟真正的使用者(受害者)用 VM 作為主力機的機率很低。 52 | 53 | 那,在 browser 上,有可能偵測 VM 嗎?也許可以喔。 54 | 55 | 試想,上一次你看到一台電腦沒有顯卡沒有內顯,是什麼時候?我人生中用的第一台電腦就已經有內顯了欸。但許多 VM 預設沒有顯卡,無論是模擬或是 GPU passthrough 都需要特別設定。因此,如果 WebGL report 中的 renderer 的值並非顯卡,就很有可能這是在 VM 之中。例如下圖是我在 PVE 中的 Windows 跑出來的結果。 56 | 57 | ![](/images/webgl-report-vm.png) 58 | 59 | 或是有時 renderer 名稱就直接暴雷了。例如下圖是我在 VirtualBox 中的 Windows 跑出來的結果。 60 | 61 | ![](/images/webgl-report-vm-2.png) 62 | 63 | 當然,做 (anti-)anti-VM 的人大概看了會笑出來,這也太容易繞過了吧。這方法雖然對專家沒用,但在多數情境下,使用 VM 的人可能都不會刻意去改規定,他們大概也不會意識到其實 web 上也可能做到(非常陽春的)VM 偵測,只覺得奇怪怎麼驗證碼一直跳而已。 64 | 65 | ## 參考資料 66 | Wu, Shujiang, et al. "Rendered Private: Making {GLSL} Execution Uniform to Prevent {WebGL-based} Browser Fingerprinting." _28th USENIX Security Symposium (USENIX Security 19)_. 2019. 67 | Cao, Yinzhi, Song Li, and Erik Wijmans. "(Cross-) Browser Fingerprinting via OS and Hardware Level Features." _NDSS_. 2017. 68 | https://browserleaks.com/webgl -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/attributes.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | title: "Passive Fingerprinting" 4 | --- 5 | 6 | # Passive Fingerprinting 7 | 我們首先從 passive fingerprinting 開始談起。Passive fingerprinting 是那些只從請求內容就可以得出的特徵。此章節介紹三種:IP Address、HTTP request header、TLS。這些特徵用於 fingerprinting 時都不會使用到 JavaScript,只需要檢視請求內容即可。 8 | 9 | ## IP Address 10 | IP address 幾乎可以說是最早用於 web tracking 的特徵,通常藉由 IP address 便可以把使用者定位在很小一個群體了。不過由於 IP address 時常變換,尤其在行動世代,使用者會一直換基地台、換 WiFi AP,偶而掛個 VPN,且大家共享 public IP address 的狀況已經是常態,IP address 已經沒什麼參考價值了。 11 | 12 | ## User Agent 13 | User agent 是夾帶於 HTTP request header 的字串,其透漏了使用者的作業系統、瀏覽器與其版本等。舉例來說,我所使用的瀏覽器目前的 User Agent 是: 14 | 15 | ``` 16 | Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0 17 | ``` 18 | 19 | 這就透漏了我是 Linux 使用者,windowing system 是 X11,然後瀏覽器是 Firefox 103.0。AmIUnique 的統計顯示只有不到 0.37% 的人跟我有一樣的 user agent。 20 | 21 | 不過因為 user agent 洩漏過多資訊而可以用來做追蹤已經廣為人知,所以許多瀏覽器開始盡可能讓 user agent不洩漏太多資訊。Chrome 從 2021 年起開始便開始陸續簡化 user agent,將小版本號(minor version number)移除,例如 `Chrome/93.0.1234.56` 變成 `Chrome/93.0.0.0`,並簡化了平台資訊,例如 `Linux; Android 9; SM-A205U` 變為 `Linux; Android 10; K`,藉由移除細部資訊使 user agent 變得不獨特。此外,也有些browser extension 可以修改 user agent 來混淆 tracker。因此 user agent 用於 fingerprinting 的價值如今已經不高。 22 | 23 | 題外話,user agent 有著非常精彩的歷史,大家可以看看這兩篇文章,感受一下 web 世界的美好:[History of the browser user-agent string](https://webaim.org/blog/user-agent-string-history/), [History of the user-agent string](https://humanwhocodes.com/blog/2010/01/12/history-of-the-user-agent-string/)。 24 | 25 | ## Accept 26 | rowser 會使用 HTTP request header `Accept` 來向伺服器表達 browser 支援哪些檔案格式,藉此讓伺服器可以決定可以回應哪種檔案格式。對於不同類型的 request 會有不同的 Accept header,而這個值會隨著 browser 以及電腦中有安裝哪些 decoder 之類的而有所影響。 27 | 28 | 例如我所使用的瀏覽器在請求一般網頁時使用的 `Accept` 值是: 29 | ``` 30 | Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 31 | ``` 32 | [AmIUnique](https://amiunique.org/fp) 的統計顯示只有三成的人跟我有一樣的 `Accept` 值。 33 | 34 | 使用 `` 請求圖片時是: 35 | ``` 36 | Accept: image/avif,image/webp,*/* 37 | ``` 38 | 39 | 使用 `` 請求 CSS 檔案時是: 40 | ``` 41 | Accept: text/css,*/*;q=0.1 42 | ``` 43 | 44 | 我不清楚 `Accept` header 在 2022 年多有效,畢竟現在大家都照標準走了,可能只有有沒有啟用一些特殊格式的 decoder 會有影響。但在十年前,那時 Safari 和 IE 的 `Accept` [都在亂寫](https://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers)(恩,IE,有多久沒聽到這名字了?),所以可以使這兩款 browser 在 fingerprinting 上格外地有特色。 45 | 46 | ## Accept-Encoding 47 | 另一個與 `Accept` 密切相關的是 `Accept-Encoding`,表達瀏覽器支援哪些壓縮格式。 48 | 49 | 例如我的是: 50 | ``` 51 | Accept-Encoding: gzip, deflate, br 52 | ``` 53 | 54 | ## Accept-Language 55 | 再一個 `Accept` 家族的成員,`Accept-Language`是使用者偏好什麼語言,這個的 entropy 就比前面其他 `Accept` 家族成員都還要高,畢竟不同人有不同的語言偏好。 56 | 57 | 例如我的是: 58 | ``` 59 | Accept-Language: zh-TW,en-US;q=0.7,en;q=0.3 60 | ``` 61 | 62 | JS 可以用 `navigator.language` 或 `navigator.languages` 拿到。 63 | 64 | [AmIUnique](https://amiunique.org/fp) 的統計顯示只有不到 0.01% 的人跟我有一樣的 `Accept-Language` 值。看來用 `zh-TW` 的人很少呢。 65 | 66 | ## 螢幕資訊 67 | 為了讓開發者可以根據螢幕的狀況調整網頁介面呈現,JavaScript 與 CSS 都可以存取視窗大小。除此之外,JavaScript 也可以取得螢幕大小。由於使用者通常不會一直換螢幕,而且大家用的螢幕大小多少有些不同,所以螢幕大小可以提供一定程度的穩定性與 entropy。與螢幕長寬不同,視窗大小通常有許多變化,數值非常多元,但相對的也就不是很穩定。更糟糕的是,當視窗不是被放到最大時,因為視窗大小的數字通常足夠獨特,若是搭配其他資訊,則追蹤者有機會將同視窗的兩個不同分頁串連起來,得知這兩個網頁是由同一個使用者所瀏覽,藉此實現 cross-site tracking。 68 | 69 | JavaScript 有幾個 API 可以存取螢幕長寬與視窗大小。螢幕長寬分別可以用 `screen.width` 與 `screen.height` 取得。視窗大小則可以用 `window.width` 與 `window.height` 取得。最後則是螢幕可用的範圍,也就是扣除畫面上方 URL bar 之類的,可以用 `screen.availTop` 與 `screen.availLeft` 取得。它與螢幕大小相比,有更多可能數值,但也更不穩定。 70 | 71 | 關於螢幕資訊還有一些其他可用資訊,例如可以用 `screen.colorDepth` 取得螢幕色彩深度,用 `screen.orientation.type` 取得螢幕方向,不過桌機都只會拿到 `landscape-primary`。 72 | 73 | 更多資訊可以到 [MDN 的文件](https://developer.mozilla.org/en-US/docs/Web/API/Screen)上看。 74 | 75 | ## Timezone 76 | 就... 時區。Javascript 可以從 `new Date().getTimezoneOffset()` 拿到。 77 | 78 | ## DNT (Do Not Track) 79 | DNT 是一個 HTTP request header 中的 entry,它用於向追蹤者表達「我不想被追蹤」,就這樣而已,它沒辦法阻止追蹤者嘗試追蹤,只能夠單純地表達使用者的意願而已,如果追蹤者執意要追蹤,DNT 什麼也做不了。 80 | 81 | DNT 在 HTTP request header 裡面是: 82 | ``` 83 | DNT: 0 84 | DNT: 1 85 | ``` 86 | 在 Javascript 中可以用 `navigator.doNotTrack` 取得。 87 | 88 | 嘲諷的是,因為設定 DNT 的人太少了,所以使用 DNT 反而有可能讓使用者在人群中特別突出(也就是給出很大的 surprisal)。因此 DNT 現在已經 deprecated 了,也不推薦大家開啟 DNT,不過基本上主流瀏覽器幾乎都還是支援 DNT,只有 Safari 已經拿掉了。 89 | 90 | ## 特殊功能啟用狀態 91 | 有點難定義何謂特殊功能,但總之對於有些不是全部人都會開啟的功能,可以拿這些資訊來做 fingerprinting。例如 cookie 可以用 `navigator.cookieEnabled` 來檢查是否有啟用,Local Storage 可以嘗試[直接寫個東西看會不會噴 exception](https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available)。 92 | 93 | 可能有些讀者會很疑惑,大家不都在使用 cookie 與 local storage 嗎?不過如同前面 DNT 時的討論,正是因為大家都在用,所以不使用的人會特別突出,於是如果有人不使用,這個 feature 就會貢獻很大的 surprisal。 94 | 95 | 96 | 這篇文章我們細數了一些常常用來做 fingerprinting 的 browser attributes。當然,正如一再強調的,這些資訊還不足以提供足夠好的 fingerprinting,接下來幾篇文章會再介紹更多 browser fingerprinting 的技術。 97 | 98 | ## TLS Fingerprinting 99 | Transport Layer Security (TLS) 是個用於將客戶端與伺服器之間的流量加密的協定。HTTPS 是在以 HTTP 為基礎並使用 TLS 加密,所以 HTTPS 幾乎必然使用 TLS。在開啟 TLS 通訊時,客戶端與伺服器會先經過 TLS handshake。TLS fingerprinting 是利用 TLS handshake 時客戶端傳送的訊息,從中尋找足夠獨特的特徵。 100 | 101 | TLS handshake 的第一則訊息是客戶端傳給伺服器一個 Client Hello,並帶有 TLS 協定版本(TLS 1.0 至 1.3)、支援的加密演算法(cipher suite)、支援的數位簽章。接下來的細節並不重要,總之最後結論是客戶端與伺服器會共同協商出雙方都可以接受的加密與簽章演算法,並得出相同的 session key。 102 | 103 | TLS fingerprinting 的重點在於,不同客戶端支援的加密與簽章演算法並不完全一樣,也有著不一樣的 TLS extensions。Firefox 使用 NSS、Chrome 使用 BoringSSL、Safari 使用 Apple Secure Transport Layer。不同的實作可能會產生不同的結果。 104 | 105 | 舉例來說,瀏覽同個網站,Chrome 與 Firefox 所選擇使用的加密與簽章演算法並不一樣。Firefox 使用 AES-256 GCM SHA 384,而 Chrome 選擇用 NIST P-256 與 AES-256 GCM。 106 | 107 | 也因為不同瀏覽器有不同的行為,即便瀏覽器偽造了 user agent,光靠 TLS fingerprinting,仍然可以猜到使用者真正的瀏覽器為何。 -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/fonts-enumeration.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 2 3 | title: "Fonts Fingerprinting" 4 | --- 5 | 6 | # Fonts Fingerprinting 7 | 每個人的電腦都裝有不同的字型,也可能有不同的預設字型,這所造成的影響,除了成為所有前端工程師的惡夢以外,也成為 browser fingerprinting 的 entropy 來源:既然每個人都有不同的字型,而且大家不會頻繁地安裝新的字型,那麼使用者設備所配備的字型便成為可用的特徵。我們將於此章介紹幾個可以找到瀏覽器裝有哪些字型的方法,以及 Fonts Fingerprinting 的危害。 8 | 9 | 在遠古時代,Flash 可以直接列舉出所有字型,但顯然現在沒有人在用 Flash 了。目前並沒有任何 API 可以直接枚舉出所有字型,也沒有 API 可以直接查詢一個字型是否存在,所以能做的只有,先列出所有我們想測試是否存在的字型,然後利用各種 side-channel 去依序測試哪些字型真的存在。 10 | 11 | ## 篩選待測字型 12 | 列出字型乍看之下並非難事,然而如何最有效率地找到足夠獨特的特徵,則不是個簡單的任務。核心要點是,所選的字型必須具有代表性。 13 | 14 | 一般而言,可以將網頁上的字型分為三類: 15 | - 網頁字型:網頁自帶的字型。因為其不來自使用者的設備,這沒辦法用於 fonts fingerprinting。然而網頁字型的功能本身可以協助 fingerprint 其他字型。 16 | - 系統字型:作業系統自帶的字型,通常使用該作業系統的裝置都會搭載該字型。雖然這可能有些用,但作業系統往往可以從其他方式做推論(例如 user agent),所以這個幫助也很有限。然而,在未來討論到一些有問題的防禦手段時,針對系統字型的 fingerprinting 可以派上用場。此外,部分作業系統會根據不同語言自動安裝不同字型,這使得特殊語言的字型可以用於 fingerprinting。 17 | - 使用者自己安裝的字型:有些字型是使用者自己安裝的,無論是主動安裝,或是因為安裝一些軟體而被安裝(例如有些辦公軟體會自帶一些字型)。這些往往是最有 fingerprinting 價值,因為其通常足夠獨特。 18 | 19 | 因此,系統字型如「蘋方」(Apple 針對中文市場內建的字型)、Roboto(Android 與 Chrome OS 內建字型)等可以提供一些關於作業系統的資訊。一些使用者必須自行安裝的字型,或是隨著如 Microsoft Office、Adobe CC 等套裝軟體而一起安裝的字型,則因足夠獨特而成為 fingerprinting 的首選。像是 Arial 或 Times New Roman 等大家都有的字型,則不會有什麼幫助而可以直接忽略。 20 | 21 | 此外,除了字型本身是否存在以外,另一個特徵是,是否有多個通常不會一起出現的字型同時出現了。例如當 Windows 的語言設定為日文或阿拉伯文時,會下載該語言的字型,但很少有裝置同時有日文的字型與阿拉伯文的字型,於是雖然兩者都不獨特,世界上有非常多日文與阿拉伯文的使用者,但兩者搭配在一起便十分獨特。 22 | 23 | 24 | ## 檢查字型是否存在 25 | 下一步是檢查字型是否存在,這邊我會討論幾個不同方法。 26 | 27 | ### 檢查邊框變化 28 | 不同字型會有不同的字寬。如下圖所示,同樣的字串如果用了不同字型會有不一樣的寬度。換言之,偵測邊界框(bounding box)改變是個測試字型是否存在的可行方法。 29 | 30 | ![](/images/font-width.png) 31 | 32 | 因此我們可以這樣測試一個字型是否存在: 33 | 1. 給定預設字串,先使用預設字型,並該字串的外框寬度(`offsetWidth`) 34 | 2. 嘗試改變該字串所使用的字型(使用 CSS 的 `font-family`) 35 | 3. 如果外框寬度改了,那就代表字型存在 36 | 4. 如果外框沒有變化,可能是字型不存在所以 fallback 回預設字型(也就是什麼有沒有變),也有可能真的就這麼剛好預設字型與測試字型的寬度一樣,造成誤判 37 | 38 | 藉由這個方式,便可以有效率地測出哪些字型存在。需要注意的是,雖然存在誤判可能性,但因為同個使用者每次都會在同個字型誤判,所以還是可以作為 fingerprint,畢竟 fingerprinting 只要求數據獨特,沒有要求數據合理。 39 | 40 | 那,預設字串應該是什麼?最常見的是 `mmmmmmmmmmlli`,因為在沒有固定寬度的英文字型中,通常 `m` 是最寬的、`l` 是最高的,所以對 bounding box 影響最大(`i` 是來增加 entropy 的)。 41 | 42 | ```javascript 43 | function font_enumeration() { 44 | const fonts = ["Microsoft YaHei", "Microsoft Yi Baiti", "MingLiU", "MingLiU-ExtB", "Times New Roman Baltic", "Times New Roman CYR", "Times New Roman", "Ubuntu", "Noto Sans CJK TC", "TW-MOE-Std-Kai", "Droid Sans", "fake-font-this-should-not-exist"]; 45 | const containerDiv = document.createElement('div'); 46 | containerDiv.style.cssText = 'position: absolute; display: block !important'; 47 | document.body.appendChild(containerDiv); 48 | 49 | const pDefault = document.createElement('p'); 50 | pDefault.innerText = 'mmmmmmmmmmlli'; 51 | pDefault.style.cssText = `display:inline !important; width:auto !important; font-size: 10px !important;`; 52 | containerDiv.appendChild(pDefault); 53 | 54 | const ps = fonts.map((font, i) => { 55 | const p = document.createElement('p'); 56 | p.innerText = 'mmmmmmmmmmlli'; 57 | p.style.cssText = `display:inline !important; width:auto !important; font: 10px "${font}" !important`; 58 | containerDiv.appendChild(p); 59 | return p; 60 | }); 61 | 62 | const results = fonts.filter((font, i) => ps[i].offsetWidth != pDefault.offsetWidth); 63 | 64 | return results; 65 | } 66 | console.log(font_enumeration()) 67 | ``` 68 | 69 | ### 嘗試寫到 Canvas 70 | Canvas API 讓 Javascript 也有基本的繪圖功能,常用於動畫、遊戲、資料視覺化等。作為繪圖功能,當然會允許在畫布上放置文字。第二種方法是直接把文字寫到 canvas 並指定字型,然後比較該 canvas 是否與使用預設字型的 canvas 一樣。通常此時會用的預設字串是 `Cwm fjordbank glyphs vext quiz`,也就是英文的 pangram,以確保每個字母的不同都會被偵測到。 71 | 72 | 有時我們會把這歸類到 canvas fingerprinting,也就是後續的主題,屆時會再做更深入的討論。 73 | 74 | ### 使用 `@font-face` 的 Fallback 機制 75 | 如果前面都要用 Javascript 覺得太無聊了,在此提供一個純 CSS 的解法。CSS 的 `@font-face` 用於自訂字型,其特色是字型可以從 local 拿也可以從 remote 下載,且可以提供 fallback 字型,也就是如果位置 A 拿不到就去位置 B 拿。舉例來說: 76 | ```css 77 | @font-face { 78 | font-family: 'Helvetica'; 79 | src: local('Helvetica'), 80 | url('https://fonts.example/Helvetica.ttf') 81 | format('truetype'); 82 | } 83 | ``` 84 | 85 | 如此則如果 local 找不到 `Helvetica`,就會去 remote 拿。 86 | 87 | 於是這就造成一個 side-channel:如果伺服器發現有個客戶端來下載字型,便意味著這個客戶端並未安裝該字型。 88 | 89 | 所以追蹤者可以這樣做。首先,當瀏覽器請求網頁時,伺服器隨機生一個 token 並在回傳的網頁: 90 | ```html 91 |
a
92 | 100 | ``` 101 | 接著,如果 `fonts.example` 收到對 `//Helvetica.ttf` 的請求,就知道被分配到該 token 的 browser 並沒有內建 Helvetica。 102 | 103 | 104 | ## 防禦 105 | 目前有兩個作法。第一種是,可以把可以使用的字型限制在一個白名單內,例如只能使用系統字型,拒絕任何使用者自行安裝的字型。由於是作業系統內建的字型,基本上使用這個作業系統的人看起來會一模一樣,或至少會和其他使用相同語言的人一樣。另一個是要求所有字型都必須要用下載的,不能使用內建的,但這個方法的可行度較低,因為可能使用更多網路流量。 106 | 107 | 偵測 fonts fingerprinting 則稍微簡單一些。大量存取各種字型的行為非常明顯,除了 fingerprinting 外也沒什麼理由需要這麼做,所以雖然無法完全避免,但至少還能偵測到追蹤者正在嘗試攻擊。 108 | 109 | ## 參考資料 110 | D. Cassel, S.-C. Lin, A. Buraggina, W. Wang, A. Zhang, L. Bauer, H.-C. Hsiao, L. Jia, T. Libert. OmniCrawl: Comprehensive Measurement of Web Tracking With Real Desktop and Mobile Browsers. In Privacy Enhancing Technologies Symposium (PETS), July 2022. 111 | Fifield, David, and Serge Egelman. "Fingerprinting web users through font metrics." _International Conference on Financial Cryptography and Data Security_. Springer, Berlin, Heidelberg, 2015. 112 | [Font Enumeration Fingerprinting](https://www.darkwavetech.com/index.php/device-fingerprint-blog/fonts-fingerprinting) 113 | [Demo: Disabling JavaScript Won’t Save You from Fingerprinting](https://fingerprint.com/blog/disabling-javascript-wont-stop-fingerprinting/) 114 | [Font fingerprinting defenses roadmap](https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/18097) -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 4 3 | title: "Browser Fingerprinting" 4 | --- 5 | 6 | # Browser Fingerprinting:如何被獨一無二的 Browser 出賣 7 | 8 | 在上一章,我們討論了 stateful tracking、tracker 需要儲存一些 identifier,以及不同隱私保護機制想辦法避免 identifier 被寫入或是存活太久。如果 identifier 其實可以不用儲存,而是用算出來的呢?那是不是意味著 identifier 沒辦法被刪掉,因為只要重新算一次就好了?這就是所謂的 stateless tracking,它有個可能更為知名的名稱:browser fingerprinting。 9 | 10 | 早從 Web 剛出現的遠古時代開始,為了改善使用者體驗,browser 會傳送一些資訊幫助網站提供客製化的網頁結果。例如:為了提供不同語言的網站給不同語言的使用者,browser 會在 HTTP request 裡面夾帶使用者偏好的語言,也可以在 JavaScript 使用 `navigator.languages` 拿到偏好語言、為了讓網頁可以根據使用者時區顯示不同時間,可以用 JavaScript 從 `new Date().getTimezoneOffset()` 拿到時區、為了讓網頁可以根據不同視窗大小調整 UI,可以用 `screen.height` 與 `screen.width` 取得螢幕長寬。 11 | 12 | 其中最重要的資訊莫過於 user agent,它用於表達使用者的作業系統、瀏覽器版本等,如此則當使用者要下載軟體時可以下載到符合其所用作業系統的版本,或是網頁可以針對不同瀏覽器做特別處理,使用一些只有特定瀏覽器支援的功能。User agent 會夾帶於 HTTP requests,也可以用 JavaScript 的 `navigator.userAgent` 取得。 13 | 14 | 另外,不同的執行環境也會影響 browser 的性質。例如:不同顯卡或驅動程式在渲染圖片上可能有些微的差異、當網頁指定某個字型時,電腦有沒有安裝該字型會決定 browser 能不能順利顯示、又或是其他硬體設備如電腦的麥克風數量差異。 15 | 16 | 上面的情境範例都反映一件事:每個 browser 多少有一些性質差異,可能是預設語言、時區或是字體。如果把 browser 的各個性質蒐集起來,便會發現很難在世界上找到兩個一模一樣的 browser。這聽起來很像是 identifier 對吧?我們將其稱為 browser fingerprinting。 17 | 18 | Browser fingerprinting 是一種藉由瀏覽器的各種特徵去建構獨一無二的 identifier,並以此識別與追蹤使用者的技術。 19 | 20 | ## Browser Fingerprinting 範例 21 | 有個很棒的網站叫做 [AmIUnique](https://amiunique.org/),可以自動對使用者的瀏覽器做幾個常見的 browser fingerprinting 方法,並算出該結果有多獨特。 22 | 23 | 下圖是我的電腦用 Chromium 算出來的結果: 24 | ![](/images/amiunique.png) 25 | 26 | 首先可以注意到,我的 user agent 非常罕見:只有 0.01% 的人跟我有一樣的 user agent。光是 user agent 就已經洩漏非常多資訊了。此外,有 28% 的使用者用 Linux,挺沒說服力的,可能因為會用這網站的人都是在乎隱私的技術人員吧。至於字型,幾乎沒人與我有一樣的字型列表。光是從截圖的部份,就可以看出我的瀏覽器有多少特徵是罕見的。更糟糕的是,在我撰文時,該資料庫有接近兩百萬筆 browser fingerprint,但沒有任何人跟我有一樣的 fingerprint,所以我的瀏覽器可以被唯一地識別出來,而這過程中沒有存取 cookie 或其他 storage,也沒有拿取任何特殊權限。 27 | 28 | ## Browser Fingerprinting 特徵的理想性質 29 | 先前我們提到了多個可以用於 browser fingerprinting 的特徵,例如 user agent 字串、系統語言等,在前面的 AmIUnique 範例中也看到許多不同的特徵。所以為什麼會選用這些特徵?在決定可以使用哪些特徵時,我們應該考慮什麼?我們可以列出三個性質:「可用」、「穩定」、「獨特」。 30 | 31 | ### 可用 32 | 若一個特徵要可用於 browser fingerprinting,第一個要件是 tracking script 必須可以存取該特徵。Tracking script 存取一個特徵時不可以太過於明目張膽,所以該特徵通常不會用到特殊權限,否則跳出奇怪的權限請求,會引起使用者懷疑與反感。此外,必須可以快速地、有效率地存取,畢竟花費兩分鐘並且使 CPU 使用率飆升的 tracking script 將非常明顯。最後,應該在所有情境下都可以使用,不能只有在特定網站或特定狀況下可以使用。 33 | 34 | ### 獨特 35 | 若要識別使用者,特徵自然要足夠獨特。如果所有使用者都沒有啟用 Flash,那「是否有啟用 Flash」大概就不會是個好的特徵,它並不足以分辨使用者。在討論一個特徵是否足夠獨特時,我們時常援引資訊理論來討論每個特徵可以貢獻多少資訊。 36 | 37 | ### 穩定 38 | 如果只要求一個特徵是可用且獨特的,有個完美符合要求的特徵:亂數。任何人都可以存取亂數產生器,只要值足夠大而且亂數夠亂,每個人拿到的數值都會非常獨特。但這顯然不是一個好的特徵,一個一直變化的東西無法作為 identifier。一個理想的特徵應該足夠穩定,每次存取都應該得到一樣或足夠類似的數值,使得其可以作為 identifier。 39 | 40 | ## Browser Fingerprinting 的特性 41 | 瞭解用於 browser fingerprinting 的特徵應該具備哪些性質後,接著可以得出 browser fingerprinting 的幾個重要的特性。 42 | 43 | ### 同時使用多個 Features 44 | 很少會遇到某個特徵可以唯一地識別使用者。舉例來說,很多人的系統語言是波蘭文,也有人的時區是 UTC+11,而作業系統是 Linux 的也有不少,但系統語言是波蘭文、時區是 UTC+11 且作業系統是 Linux 的人很少。基本上現代的 browser fingerprinting 都是非常多特徵的聚合,才能達到理想的準確率。 45 | 46 | ### 不需要特殊權限 47 | 如同前述,browser fingerprinting 所使用的特徵通常不仰賴特殊權限。這些不需要特殊權限的功能之所以存在,是因為一般而言這些資訊不會有危害,也不是設計來做 web tracking 的,所以理應可以放心地讓 JavaScript 讀取,可是這些資訊卻出乎意料地成為可以用於 web tracking 的特徵,或是成為其他特徵的 side channel。因為個別特徵的存取不需要特殊權限,整個 browser fingerprinting 程序當然也就不需要特殊權限,這使得使用者在不知不覺之間就被 fingerprint。 48 | 49 | ### 不保證唯一與永久不變 50 | 雖然前面提到 browser fingerprinting 的特徵講求獨特與穩定,但這只是理想性質,並非永遠可以達成。 51 | 52 | 首先,browser fingerprinting 所建構出來的 identifier 無法保證獨一無二。雖然機率不高,但的確可能遇到兩個 browser 剛好在這些特徵上完全一樣。在後續章節我們會看到有些 browser fingerprinting 的防禦手法正是建立在創造一模一樣的特徵上。 53 | 54 | 此外,browser fingerprinting 並不是永久不變的。許多微小的變化都可能使 fingerprint 改變,例如換了一個螢幕、更新驅動程式、更新瀏覽器、裝了一個新的 extension,這些小事都可能讓 fingerprint 改變。不過這並不意味著無法用於追蹤,如果改變足夠小,還是有可能把兩個 fingerprint 連結在一起。在後續我們也會談到如何同時使用 cookie 與 browser fingerprinting 追蹤使用者,藉此延長有效追蹤時間。 55 | 56 | ### 難以防禦 57 | Browser fingerprinting 非常難以防禦。若說阻擋 third-party cookie誤殺許多正當使用情境,那阻擋可以用於 browser fingerprinting 的特徵幾乎可說是破壞整個 web 生態。拒絕讓網頁存取使用者的時區與語言偏好,拒絕讓網頁渲染圖片,拒絕供網頁使用特殊字型,都將使如今非常蓬勃的 web 生態不復存在。 58 | 59 | 此外,有許多意料之內與之外的管道可以洩漏資訊。有許多現在很常見的可以用於 browser fingerprinting 的技術,在當初發展時多半從未考慮過可能被用於 browser fingerprinting。只要瀏覽器仍在演進,便會有越來越多功能,也就有越來越多可能洩漏資訊的管道。 60 | 61 | 儘管如此,仍然有不少有效的防禦手段,只是在這背後有更多已經被證實為無效或甚至有害而被淘汰的手段。抵禦 browser fingerprinting 絕非易事,在往後的章節我們也會做更多討論。 62 | 63 | 64 | ## Active 與 Passive Fingerprinting 65 | 稍早提及,browser fingerprinting 的特徵應該要是可用的,也就是容易取得的。我們可以再根據如何取得特徵值,將 browser fingerprinting 分成 active 與 passive。 66 | 67 | Passive fingerprinting 所使用的特徵可以直接在網頁請求的內容中看到,不需要另外在客戶端執行程式。常見的如 HTTP request header(其中包含 user agent)、IP address 等。在有些人口較小的情境,光是 user agent 與 IP address 就有機會唯一地識別出個別使用者了,所以即便這兩個特徵看似籠統與無害,在一些情境下仍然可以達成有效的 web tracking。這類型的 fingerprinting 在瀏覽器送出請求時就完成了,在前端幾乎毫無跡象,因此也難以防範。 68 | 69 | Active fingerprinting 則是會在網站上插入 tracking script,也就是需要執行一些 JavaScript 來獲得特定特徵。多數 browser fingerprinting 技術都屬於此類型,它們需要在瀏覽器上執行一些程式,這也使得這些行為容易被偵測到並封鎖,但相對的,藉由 active fingerprinting 通常可以獲得更準確與持久的資訊。 70 | 71 | ## Browser Fingerprinting 的防禦策略 72 | 要如何防禦 browser fingerprinting 而同時不大幅降低使用者體驗,是個艱難的問題。此段我們將只先簡單提出幾個可能的防禦策略。對於個別 browser fingerprinting 技術的防禦將在介紹攻擊手段時一起介紹。更仔細的防禦策略分析則會延後到後續的專章。 73 | 74 | 前文曾提及,一個有效的特徵應該具備可用、穩定、獨特三個特性,缺一不可。換言之,只要能使其中一個性質消失,該特徵將不再能用於 browser fingerprinting。這正是防禦的三大策略。 75 | 76 | 第一種策略是移除可用性。換言之,使一個 API 變得不容易存取,或是改成需要特殊權限才能存取。例如 Tor Browser 將 Canvas API、WebAudio API 等功能變成需要授權後才能執行,藉此避免其被用於 fingerprinting。 77 | 78 | 第二種策略是移除穩定性,也就是使一個特徵的數值變得不穩定,例如加上隨機雜訊。然而,直接加上隨機雜訊不一定有效,因為可能會: 79 | 1. 洩漏這個使用者正在嘗試避免被 fingerprint; 80 | 2. 取樣多次後平均,雜訊可能會互相抵銷。 81 | 因此更常見的作法是針對每一個 eTLD+1 都有固定的雜訊,且在 cookie 被清空時重置。前者確保 cross-site tracking 無法實現;後者確保 browser fingerprint 最多和 cookie 一樣持久,不會更久,也就使 browser fingerprinting 無法幫助 cookie respawning。 82 | 最後則是移除獨特性,也就是讓瀏覽器的行為不再有歧異,若所有瀏覽器的行為都一致,則不再有可供 fingerprinting 的資訊洩漏。 83 | 84 | 此文是 browser fingerprinting 系列文章的第一篇,也意味著我們告別了 stateful tracking,來到了新的篇章。期待大家會喜歡接下來的文章。 85 | 86 | ## 參考資料 87 | [AmIUnique](https://amiunique.org/) 88 | [Fingerprint Randomization](https://brave.com/privacy-updates/3-fingerprint-randomization/) 89 | [Browser Fingerprinting: An Introduction and the Challenges Ahead](https://blog.torproject.org/browser-fingerprinting-introduction-and-challenges-ahead/) -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/cpu-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 9 3 | title: "CPU Fingerprinting" 4 | --- 5 | 6 | # CPU Fingerprinting 7 | 如果前面兩篇 browser extension 的 fingerprinting 還沒有嚇倒你的話,從這一篇開始,我們會進入一個很瘋狂的狀態,一種「天啊這樣也行喔?」的狀態。 8 | 9 | 那,首先就從 CPU fingerprinting 開始講起吧。這篇文章會用到一些計算機結構的基礎知識,都是非常簡單的東西,所以只要是我在修計算機結構時聽過的詞彙,就不會多加解釋,畢竟我金魚腦,這麼多年前修課而且現在還記得,應該就代表多數有修過計結的都會記得... 吧? 10 | 11 | ## 何謂 CPU Fingerprinting 12 | 雖然說是 CPU fingerprinting,但我們不是要去 fingerprint 每個 CPU 之間的些微差異,而是要去識別一個 CPU 的重要特徵,像是幾個 core、cache 多大等,藉此作為 fingerprint 的一部分。 13 | 14 | CPU fingerprint 除了拿來做 web tracking 以外,也有其他用途,像是情蒐之類的,例如這幾年很多基於 speculative execution 和 out-of-order execution 的各種 side channel attack,如果可以了解對象所使用的 CPU,便可以發動攻擊。 15 | 16 | 不過 CPU fingerprint 之所以說是瘋狂,就在於它很難!當代的 browser 基本上都有很嚴密的 sandbox,可以執行的指令很受限,盡可能避免 browser 上執行的任何程式可以獲得過多的資訊。基本上在 browser 上能做的也就只有 Javascript 與 WebAssembly,頂多運用 Just-in-time compiler 的特徵來做更多事情,但仍非常受限。此外,如果要在 browser 上做針對 CPU 的 timing attack 或其他 side-channel attack,會受到許多影響,畢竟 browser 自己就有非常受限的 scheduling 的機制、OS 再一層 scheduling,再加上各種 noise,許多有用的資訊都被掩埋在雜訊中了。所以,要在 browser 上執行一些程式來洩漏 CPU 資訊沒有那麼 trivial。 17 | 18 | 19 | ## CPU Fingerprinting 技術簡介 20 | 接下來我們將開始介紹幾個可以 fingerprint CPU 的技巧。 21 | 22 | ### CPU Core 數量 23 | CPU Core 數量理應可以直接用 `navigator.hardwareConcurrency` 取得,不過問題沒這麼簡單。當代的 CPU 多半會透過一些方式讓一個 core 可以偽裝的很多個 core,例如常常聽到有人介紹一個 CPU 是 `4C8T`,便意味著這個 CPU 其實只有四個 core 但偽裝成八個 core。我們稱真正的、物理的 core 為 physical processor cores,偽裝出來的為 logical processor cores,一個 4-core CPU 可能有八個 logical processor cores,也就是可以同時跑八個 thread 而不需要做 context switch。`navigator.hardwareConcurrency` 回報的是 logical processor cores 的數量。比較麻煩的是,這個數字有時會騙人,例如當使用者想要避免 browser 佔用太多 CPU 時可能就會故意低報 logical processor cores 的數量,藉此限制 Worker 數量。 24 | 25 | 等等,Worker 是什麼?眾所皆知的,Javascript 是個 single thread 的語言,之所以感覺很多事情一起做,只是 event loop(Javascript 的 concurrency model)在跑而已,並非真的使用多個 thread。但總有情境是真的需要 multi-thread,Worker 便因此而生。反正重點是我們可以用 Worker 作到 multi-thread。 26 | 27 | 一個簡單但不那麼完美的作法是,一開始先 spawn 一堆 worker,把 CPU 佔滿,於是為了要做 multithreading,不得不讓一些 worker 先躺回去 idle state 排隊等待,大家輪流使用有限的 thread。於是,如果我們生出來的 worker 數量少於 CPU logical processor core 的數量,理論上 worker 應該跑得差不多快,但如果生出來的 worker 太多,超過 CPU logical processor core 的數量了,那因為大家要輪流跑,worker 的完成時間就會不太一樣,而且會變長。此外,因為 logical processor core 終究是偽裝出來的,一個 physical processor core 跑一個或兩個 logical processor core 時的執行速度不一樣,所以一樣可以藉由執行時間觀察出 physical processor core 的數量。 28 | 29 | 不過因為出來的數字很亂,所以還需要做很多後續處理,這就不是這篇文章可以涵蓋到的部份了。 30 | 31 | ### Cache Size 32 | 不同的 CPU 會有不同的 cache 大小,所以藉由偵測 cache 大小一樣可以來 fingerprint CPU。舉例來說,通常 ARM 只會有兩層 cache(L1, L2),而 x86 有三層 cache(L1, L2, L3),尤其 L3 大小因 CPU 而有很大的差異。 33 | 34 | 正如過去針對 cache 的攻擊八九不離十是 timing attack,我們現在要做的也是去偵測 cache 存取速度來判斷其大小。若要偵測 L1 cache 大小,可以嘗試不斷寫資料,同時回頭讀以前的資料,看相隔多遠時速度開始變慢了,便知道 L1 cache 有多大。同樣的,當 L1 與 L2 都滿了的時候,速度會再次變慢,當 L3 也滿了,就也會再次變慢,如果超過合理大小時都沒有變慢,大概就是沒有 L3 cache。 35 | 36 | Trampert 等人提出了一個有趣的方法是:先隨機生成大小為 k 的 circular linked list,從 head 開始,接著往前走 N 個 node(N 是常數,N 會遠大於 circular linked list 的 node 數量),看 k 多大時存取時間開始變長了。這個方法之所以有效,在於其迫使這中間每個 node 都一定要被存取,所以這些 node 不得不被放入 cache。如果所有 node 可以被塞進 L1 cache,理應走 N 個 node 都會一直是 cache hit(撇除第一次讀取),但如果塞不進 L1 cache,那路途中會一直遇到 cache miss,導致執行時間變長。於是同樣是 N 步,k < cache size 時跑很快,k > cache size 時跑很慢,就可以藉此偵測 cache size。 37 | 38 | ### Cache Associative 39 | 上述的演算法除了偵測 cache size 以外,其實也可以偵測 (L1 data-)cache associative。如果我們讓每個 node 之間都相隔一個 cache-size,那在寫入 cache 時就會寫到一樣的地方(因為 mod 結果一樣),於是,一開始往前走時會一直 cache hit,因為 cache 中該 set 還沒有滿,然而一旦超過 associativity,該 set 也就滿了,會 flush 掉東西,開始發生 cache miss,導致時間增加。藉由 k 為多少時執行時間開始變長,便可推論 cache associative。 40 | 41 | ### TLB Size 42 | 又是同個概念,用上述演算法,但每個 node 相隔一個 page-size 以上,當 linked list 所使用的 page 數量小於 TLB 大小,則 TLB 沒有被塞滿,應該會一直 cache hit,但如果 linked list 用太多個 page,TLB 滿了,就會開始出現 cache miss 拉長執行時間。藉此便可知道 TLB 大小。 43 | 44 | ### Page Size 45 | 基本上也就一樣是不斷去戳固定間隔(通常是 256B)的 address,藉由執行時間就可以知道有沒有發生 page fault。 46 | 47 | ### AES 加速 48 | 早期許多高階 CPU 會有內建的 AES 加速(例如 Intel 的 AES-NI),於是可以利用 AES 的執行速度判斷有沒有硬體加入。嚴格來說,可以藉由比較 AES 速度與一個已知的計算(例如 Saito 等人用 Monte Carlo 算圓周率來比較)的速度比例來偵測,對沒有 AES 硬體加入的 CPU 來說,CPU 越快,做已知計算與 AES 的速度應該會一起變快,所以比值差不多,但如果一個 CPU 有 AES 硬體加速,則 AES(相較於已知計算)會異常地快,比值變超大,因此可以藉由兩個計算的時間比值判斷有沒有硬體加速。 49 | 50 | 但因為當今幾乎所有 CPU 都內建 AES 加速,所以這偵測已經沒什麼用了,反正大家都有,沒啥 entropy 可言。 51 | 52 | ### Benchmarking 53 | 顧名思義,直接執行一段 code 看要跑多久,來偵測 CPU 的效能。因為 multi-core 的效能比較難在瀏覽器上偵測,所以通常會用 single-core 的效能來判斷。 54 | 55 | 此外,如果有使用一些 dynamic frequency scaling 的功能(像是 Intel Turbo Boost 或 AMD PowerTune),會因為不同 task 透過 dynamic frequency scaling 得到的優勢不同,於是有沒有 dynamic frequency scaling 會讓 benchmark 的結果分佈不太一樣,於是就可以藉此看出一個 CPU 是否有使用 dynamic frequency scaling 技術。 56 | 57 | ## 如何有個精準的 Timer 58 | 前面的各種討論其實都有個隱含的前提:有個精準的 timer。但這其實不太簡單,Javascript 內建的 timer(例如傳統的 Date API 與新的 performance API)多半難以提供足夠準確的 timer,Date API 基於 Unix timestamp 本來就很受限,Performance API 為了避免用於攻擊,刻意降低精準度,可能只有 microsecond 級別,不足以拿來做 cache-timing attack。 59 | 60 | 一個意料之外但非常準確的 timer 是 Worker 的 `SharedArrayBuffer`。它原先是用於 thread 之間的 memory sharing,也就可以讓 main thread 和 worker thread 可以共享資料。但如果在 worker 裡面跑個無限迴圈去遞增 `SharedArrayBuffer` 裡面的一個數值,然後再從 main thread 去讀該記憶體空間,就可以透過數值大小來推測執行時間。於是這樣我們就有個很精準的 timer 了。 61 | 62 | 更多資訊可以參考 [XS-Leaks Wiki](https://xsleaks.dev/docs/attacks/timing-attacks/clocks/) 上的討論。 63 | 64 | ## 防禦與討論 65 | 很遺憾的,就我所知目前並沒有防禦 CPU fingerprinting 的手段。雖然移除精準的 timer 可能有些幫助,但要避免出現意料之外的 timer 也沒有這麼容易(看看 `SharedArrayBuffer`)。 66 | 67 | 不過換個角度想,CPU fingerprinting 真的這麼有效嗎?對於要做攻擊的人來說,知道一個 CPU 可能受到 Spectre 影響,是很有價值的資訊,但對於做 web tracking 的人而言,有數百萬計的 CPU 都是一樣的參數啊!CPU core 數量大概也就是那些,L1 TLB 通常是 64 個 4KB 的 page,cache associative 通常都是 8,幾乎所有人都有 AES 硬體加入與 dynamic frequency scaling,知道這些資訊能貢獻的 entropy 其實很少。(有趣的是 Apple Silicon 系列的數字通常與眾不同) 68 | 69 | 所以對於 CPU fingerprinting 也不用過於憂心,雖然技術有趣,但其所貢獻的 entropy 其實不高。 70 | 71 | ## Reference 72 | Trampert, Leon, Christian Rossow, and Michael Schwarz. "Browser-based CPU Fingerprinting." _European Symposium on Research in Computer Security_. Springer, Cham, 2022. 73 | Saito, Takamichi, et al. "Estimating CPU features by browser fingerprinting." 2016 10th International Conference on Innovative Mobile and Internet Services in Ubiquitous Computing (IMIS). IEEE, 2016. 74 | https://xsleaks.dev/docs/attacks/timing-attacks/clocks/ -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/browser-fingerprinting-and-information-theory.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 1 3 | title: "Browser Fingerprinting 與 Information Theory" 4 | --- 5 | 6 | # Browser Fingerprinting 與 Information Theory 7 | 上篇文章我們討論到 browser fingerprinting 需要使用許多 feature,每個 feature 的有用程度可能不太一樣。那,有沒有什麼方法可以衡量一個 feature 是否有用?每次 fingerprinting 時追蹤者對於使用者的認識增加程度都一樣嗎?還是有可能追蹤者拿到了 fingerprint 卻發現它沒什麼用?其背後都是在講同一件事情:browser fingerprinting 洩漏了多少「資訊」。於是我們進入了 Information Theory 的世界。 8 | 9 | Information Theory 通常被翻譯成「資訊理論」,跟 theory of computation 計算理論不一樣,不要搞混了。Information Theory 出自 Claude Shannon 的 *A Mathematical Theory of Communication*,當時主要是在通訊的情境下做討論,但如今 Information Theory 已經四處可見,從最早的衡量通訊頻道可以承載多少資料、如何壓縮資料,到 Machine Learning 裡面討論每個神經元可以儲存多少資料,密碼學裡面有沒有什麼演算法可以抵擋無限計算資源與時間的攻擊者等,都是 Information Theory 的應用。 10 | 11 | 今天我們要討論的是,Information Theory 如何應用於 browser fingerprinting 的情境中。這篇文章會有一些數學,如果讀者對這些沒興趣,可以大膽地跳過這篇,對於後續文章的理解不會有太大的影響。 12 | 13 | ## Information 14 | 相信大家都知道 bit 是什麼。一個 boolean 是 1 bit,一個 ascii 會吃掉 8 bits。 15 | 16 | 所以「有啟用 cookie」是 1 bit。 17 | 18 | 然後我的 user agent 如下, 19 | ``` 20 | Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0 21 | ``` 22 | 有 70 bytes,所以是 70 * 8 = 560 bits。 23 | 24 | 所以我只要蒐集 user agent,就可以拿到 560 bits 的資訊量。換言之,這可以容納 2^560 種可能,space 這麼大,根本不可能重複,對吧? 25 | 26 | 不是這樣的。甚至,AmIUnique 說有 0.37% 的人和我的 user agent 一樣,遠大於 1/2^560。 27 | 28 | 我們可以這樣想:是的,儲存 `Mozilla/5.0` 佔用了 11 * 8 = 88 bits,但這個 88 bits 真的有派上用場嗎?所有主流 browser 的 user agent 都是 `Mozilla/5.0` 開頭啊,那儲存這個資訊對於我辨認使用者根本沒有幫助。 29 | 30 | 至此我們已經得到第一個觀念:重點不在於有多少 bit,而是有多少 bit 可以幫助辨識使用者。 31 | 32 | ## Surprisal 33 | 可是,一個 bit 有沒有幫助並不是 binary 的,不能說「這個 bit 有幫助」、「那個 bit 沒幫助」。在剛剛我們看到 user agent 的前 11 個字,因為大家都一樣,所以沒有幫助。但試想,如果突然出現一個人的 user agent 開頭是 `Zbmvyyn\3-1`,是不是超級顯眼?換言之,並不是前 88 bits 沒有用,而是當前 88 bits 是 `Mozilla/5.0` 沒有用! 34 | 35 | 如果前面的舉例太 technical,我們可以換個日常一點的舉例。試想,現在有個超級好學生,從不遲到,上課總是認真做筆記,回家認真寫作業,以及另一個爛學生,瘋狂蹺課,作業都用抄的。有一天我上課前走進教室,看到有個人坐在裡面,請問那是好學生會讓我比較訝異,還是那是爛學生會讓我比較訝異?肯定是爛學生吧。如果好學生在裡面,我一點都不會意外,但如果是爛學生在裡面,我會很錯愕想說他怎麼會來上課,是不是今天要期中考了。同樣是出席與否(1 bit),當爛學生沒出席時,不會發生什麼事,沒人在意,但當爛學生出席時,會讓我非常 surprise,因為這太特殊、罕見、獨特了,他竟然會來上課!至於好學生,剛好反過來,一樣是 1 bit 的資訊,他出席沒人在乎,他不出席的話,大家會很緊張他是不是怎麼了。 36 | 37 | 這就是 surprisal 的概念:當我看到這個結果時,我會多訝異。或是稍微正式一點來說:這個結果有多獨特、它蘊含了多少資訊。一個越不可能發生的事件,發生時我們獲得的資訊量就會越大,正如那個不來上課的爛學生,如果他來上課了,肯定是要出大事了。 38 | 39 | 握緊手把,我們要來看數學了。 40 | 41 | 若 {{< katex >}}F{{< /katex >}} 是個 browser fingerprinting 的演算法,當遇到 browser {{< katex >}}x \in X{{< /katex >}} 時,{{< katex >}}F(x){{< /katex >}} 的輸出是 {{< katex >}}f_n, n \in \{0, 1, \dots, N\}{{< /katex >}} ,並遵循 discrete pdf {{< katex >}}P(f_n){{< /katex >}},則每一個輸出的 surprisal {{< katex >}}I{{< /katex >}} 可以如此表達: 42 | 43 | {{< katex display >}} 44 | I(F(x) = f_n)=-\log_2(P(f_n)) 45 | {{< /katex >}} 46 | 47 | 讀者可能覺得莫名其妙,這公式哪裡飛來的。為了不讓這篇文章變成數學地獄,我們只需要用直覺理解就好。{{< katex >}}P(f_n){{< /katex >}} 是個機率,介於 0 到 1 之間,所以 {{< katex >}}I{{< /katex >}} 介於 {{< katex >}}\infty{{< /katex >}} 到 0 之間。如果機率接近於 0 的事件發生,我們會接近無限地訝異,如果機率接近於 1 的事件發生,我們一點都不在乎,訝異程度接近 0。如果對 log 函數不熟,可以參考[這張函數圖](https://www.desmos.com/calculator/os0icki7h5),其中 {{< katex >}}x{{< /katex >}} 軸是發生機率,{{< katex >}}y{{< /katex >}} 軸是 surprisal。 48 | 49 | 所以至此我們已經有第二個重要觀念了:surprisal 的是衡量某個值的罕見程度,越罕見的值蘊含越多資訊,對於辨識使用者的幫助越大。 50 | 51 | ## Entropy 52 | 但故事還沒結束。我們可以設想一個情境:全世界剩下一個 browser 還支援 Flash,於是「支援 Flash」的 surprisal 非常大,它非常獨特,可是剩下來一百億個 browser 都不支援 Flash,surprisal 接近於 0。所以追蹤者在做 fingerprinting 時應該偵測是否支援 Flash,因為可能會遇到超級大的 surprisal,當剛好遇到那個唯一支援 Flash 的 browser,就可以超級準確地識別出他。好像哪裡怪怪的吧?對於幾乎所有人,那個 bit 是完全被浪費的,它什麼也沒講。除了其中一個特例以外,所有 browser 都不支援 Flash,知道不支援 Flash 幾乎毫無幫助,卻要浪費 1 bit 去存這個垃圾。顯然用 1 bit 換取偵測到百億分之一的特例不太划算。 53 | 54 | 有沒有什麼方法可以衡量一個 feature 有多划算?一個很直覺的作法是:不如我們取 surprisal 的期望值?這就是 entropy(通常以 {{< katex >}}H{{< /katex >}} 表示)的概念。 55 | 56 | {{< katex display >}} 57 | H(F) = -\sum^N_{n=0}P(f_n)\log_2 (P(f_n)) 58 | {{< /katex >}} 59 | 60 | 需要注意的是,entropy {{< katex >}}H{{< /katex >}} 是 function of the distribution,其 input 是一個 distribution function,不是特定的值,output 也只根據 {{< katex >}}F{{< /katex >}}(browser fingerprinting algorithm)與 {{< katex >}}X{{< /katex >}}(browser 的 random variable),與遇到哪個 browser({{< katex >}}x{{< /katex >}})無關。 61 | 62 | 以上面的例子來說,因為只有百億分之一的機率可以遇到唯一一個支援 Flash 的瀏覽器,所以「是否支援 Flash」的 entropy 是: 63 | 64 | {{< katex display >}} 65 | H(F) = -((\frac{10^{10}-1}{10^{10}})\log_2(\frac{10^{10}-1}{10^{10}}) + (\frac{1}{10^{10}})\log_2(\frac{1}{10^{10}})) = 3 \times 10^{-9} 66 | {{< /katex >}} 67 | 68 | 1 bit 的資料平均而言只貢獻了 {{< katex >}}3\times10^{-9}{{< /katex >}} bit 的資訊,聽起來不太划算。 69 | 70 | 我們來討論另一個情境:使用者是否使用 Chrome。根據[統計](https://gs.statcounter.com/),有 65% 的使用者使用 Chrome,所以「是否使用 Chrome」的 entropy 是: 71 | 72 | {{< katex display >}} 73 | H(F') = -(0.65 \log_2(0.65) + 0.35 \log_2 (0.35)) = 0.934 74 | {{< /katex >}} 75 | 76 | 同樣是 1 bit 的資料,「是否使用 Chrome」貢獻了 0.934 bit 的資訊,和「是否使用 Flash」那趨近於 0 的資訊量相比,顯然「是否使用 Chrome」是個更好的 feature。 77 | 78 | 79 | 所以話說回來,entropy 到底是什麼?Entropy 用於衡量我們對資訊的 certainty。越是肯定的事情 entropy 會越小,越是不肯定的事情 entropy 會越大。試想,我們非常肯定 user agent 的前 88 bit 是 `Mozilla/5.0`,也非常肯定幾乎所有 browser 都不支援 Flash,所以這兩個 feature 的 entropy 都會很小。同時,我們沒有很肯定 browser 的預設語言是什麼,有可能是中文英文德文之類的,沒有很肯定使用者的時區是什麼,也不太肯定使用者的瀏覽器是不是 Chrome,因此這些 feature 的 entropy 很大。 80 | 81 | 至此我們可以看出重點了:如果可以用很少 bit 取得很大的 entropy,代表這個 feature 很划算。 82 | 83 | 雖然讀者可能不需要知道,但還是提一下:Shannon 證明了 1 bit 的 entropy 不可能超過 1 bit,所以別期待有可能存在 feature 是 entropy 比所需空間還要更大的,數學上可以證明這不可能存在。 84 | 85 | 最後提一下,如果要辨識出地球上每一個人,需要的 entropy 為 33 bit,因為 {{< katex >}}2^{33} > 8 \times 10^9{{< /katex >}}。 86 | 87 | ## Anonymity Sets 88 | Shannon's Entropy 的缺點在於,它很難理解。我可以理解 2 bit 的 entropy 比 3 bit 還要少,但所以 2 bit 到底是什麼概念?為了更直觀的理解,另一種討論途徑是使用 anonymity sets 的概念。 89 | 90 | Anonymity set 的概念是:把所有相同 attribute 的 browser 都 cluster 在一起。所以如果一個 anonymity set 非常大,意味著有非常多 browser 擁有一模一樣的 fingerprint,於是追蹤者就無法辨別個人身份,他只知道這一整群人裡面有其中一個人做了什麼,但他不知道是誰,或甚至追蹤者連 anonymity set 裡面有多少人都不知道。因此在抵禦 browser fingerprinting 的討論中,有許多嘗試是把 browser 盡可能地塞入大一點的 anonymity set,使其淹沒在人海之中,就很難被 track 了。 91 | 92 | 93 | ## 參考資料 94 | Eckersley, Peter. "How unique is your web browser?." _International Symposium on Privacy Enhancing Technologies Symposium_. Springer, Berlin, Heidelberg, 2010. 95 | [Understanding Your Browser Fingerprint, Or, a Basic Introduction to Information Theory](https://elliott-king.github.io/2020/07/fingerprinting-ii/) -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/techniques/webrtc-and-media-device-fingerprinting.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 5 3 | title: "WebRTC Fingerprinting + Media Device Fingerprinting" 4 | --- 5 | 6 | # WebRTC Fingerprinting + Media Device Fingerprinting 7 | 此章將介紹兩個與 WebRTC 有關的 browser fingerprinting 技術。WebRTC fingerprinting 利用 WebRTC 的設計,有可能洩漏使用者的 public 與 local IP address,甚至即便使用 VPN 亦可能洩漏真實 IP address,不過這個漏洞已經被修補。Media device fingerprinting 則是利用了 browser 可以取得攝影機、麥克風等多媒體設備的特性,枚舉出多媒體設備並以此作為 fingerprint。 8 | 9 | WebRTC (Web Real-Time Communication) 是一個允許網頁取得影音媒體、串流影音(或任意資料)的 API。最常見的應用是線上會議軟體,網頁擷取了攝影機與麥克風的輸入,並將其串流出去。另外一個應用是在瀏覽器上做 P2P(peer-to-peer)的檔案傳輸。WebRTC 的一大特色是,這是一個 P2P 的傳輸協定,也就是資訊串流不經過伺服器,而是直接在兩個瀏覽器之間互相傳輸。 10 | 11 | ## WebRTC Fingerprinting 12 | WebRTC 使用 P2P 通訊,為了找到兩端之間的路徑,勢必需要知道對方的位置。可是如果客戶端躲在 NAT1 裡面或防火牆後面,便拿不到 public IP address,也就無法告訴對方自己在哪邊,因而無法建立 WebRTC 連線。為了確保 WebRTC 在這種情境下仍然可以建立連線,開發者可以使用 ICE(Interactive Connectivity Establishment)。ICE 旨在尋找最佳的連線路徑。首先,ICE 嘗試使用作業系統和網卡提供的 IP address 連線,如果裝置在 NAT 後面,當然會失敗;接著 ICE 會藉助STUN(Session Traversal Utilities for NAT)伺服器取得 public IP address;如果仍然不行,ICE 會再藉由 TURN(Traversal Using Relay NAT)中繼伺服器轉送資料。STUN 伺服器的任務很簡單:檢查請求的 IP address,並將其回傳給請求者,從而使請求者可以知道自己的 public IP address 與 port。如果 ICE 可以成功得到有效的連線資訊,便能提供 WebRTC 使用,以此建立 P2P 連線。此外,為了讓內網之間的兩端在建立連線時,可以不用經過外網繞一整圈,ICE 也會掃描所有網路介面(interface)並取得對應的 local IP,以偵測有沒有不繞外網一大圈的路徑。上面的描述有著許多簡化,不過此處重點是:藉由 STUN server,WebRTC 可能可以拿到所有介面的 local 與 public IP address,並且有可能因為存取了 VPN client 以外的介面而繞過 VPN,即便躲在 VPN 後面,一樣可以拿到真實的 public IP address。 13 | 14 | 更完整的討論可以看這篇文章:[真实世界中的WebRTC:STUN, TURN and signaling](https://michaelyou.github.io/2018/08/01/%E7%9C%9F%E5%AE%9E%E4%B8%96%E7%95%8C%E4%B8%AD%E7%9A%84WebRTC/)。 15 | 16 | 因此,理論上,在建立 WebRTC connection 時,browser 可以獲得 public 與 local IP address。這便可以作為 fingerprinting 的來源。 17 | 18 | 常見的實作大概會長[這樣](https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only): 19 | ```javascript 20 | let pc = new RTCPeerConnection({ 21 | iceServers: [] 22 | }); 23 | 24 | //create a bogus data channel 25 | pc.createDataChannel(""); 26 | 27 | // create offer and set local description 28 | pc.createOffer(function(sdp) { 29 | sdp.sdp.split('\n').forEach(function(line) { 30 | if (line.indexOf('candidate') < 0) return; 31 | line.match(ipRegex).forEach(console.log); 32 | }); 33 | 34 | pc.setLocalDescription(sdp, () => {}, () => {}); 35 | }, () => {}); 36 | 37 | //listen for candidate events 38 | pc.onicecandidate = function(ice) { 39 | console.log(ice?.candidate?.candidate); 40 | }; 41 | ``` 42 | 43 | 例如,browser 可能會收到這樣的東西 44 | ``` 45 | candidate:4234997325 1 udp 2043278322 192.168.0.56 44323 typ host 46 | ``` 47 | 48 | 如果真的執行了這段 code,會發現你沒有收到 local IP。因為這個洞被修掉了,賀! 49 | 50 | ### 防禦:mDNS 51 | 接下來我想討論一下 WebRTC 洩漏 local IP 的防禦:mDNS(Multicast DNS)。如果說 DNS 是想讓整個 global 的網路上可以有個 domain name 與 public IP address 的 mapping ,那 mDNS 的目標就是讓 local 的網路上也可以有個 hostname 與 local IP address 的 mapping,除此之外,我們還希望不要有個集中管理的設施(像是 DNS server),不然維護成本太高了。舉例來說,現在如果要存取內網的 Chromecast,我可能要背起來它的 IP 是 `192.168.1.123`,但如果有 mDNS,我可以將其命名為 `chromecast.local`,以後用這個名字就可以存取 Chromecast 了。 52 | 53 | mDNS 的運作模式正如其名,使用了 multicast 的方法。當有台電腦想知道 `chromecast.local` 是誰時,會直接傳訊息給 multicast address,其他設備收到封包後就檢查這是不是要找自己的,而 Chromecast 收到之後發現這是在找它,所以就再 multicast 一次公告自己的 local IP address。透過這種方法,不需要有個 DNS server,也可以讓所有設備透過 hostname 找到彼此。 54 | 55 | 藉由 mDNS,WebRTC 不再需要知道自己的 local IP address,只需要知道 mDNS 的 hostname 即可。目前主流實作是,每次都會生成新的 hostname,確保不會被 track: 56 | ``` 57 | candidate:2573043965 1 udp 2113937151 53b8d70d-02af-409d-a584-761589541922.local 51605 typ host generation 0 ufrag DFSx network-cost 999 58 | ``` 59 | 60 | 因此透過 local IP address 追蹤已經不可行了。 61 | 62 | 至於 public IP address 洩漏。在沒有 VPN 的情況下不是個問題,因為追蹤者本來就拿得到 public IP address。在有 VPN 的情況下,重點在於要讓瀏覽器使用系統預設的 public interface,此問題就我所知應該也已經被修得差不多了。 63 | 64 | 65 | 66 | ## Media Device Fingerprinting 67 | 另一個與 WebRTC 相關的 fingerprinting 技術是 media device fingerprinting。試想,在許多網頁會議軟體(e.g. Google Meet)上,都可以調整輸入與輸出設備,假設我有插耳機,就可以選擇用耳機當作聲音輸出,電腦內建的麥克風作為聲音輸入之類的。通常這種功能是利用 [MediaDevices API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices) 達成的,此 API 可以列舉所有輸入輸出設備,然後選取特定設備作為訊號來源或是目的地。 68 | 69 | 例如,若要列出所有設備,可以這麼做: 70 | ```javascript 71 | navigator.mediaDevices.enumerateDevices() 72 | .then((devices) => { 73 | devices.forEach((device) => { 74 | console.log(`${device.kind}: ${device.label ? device.label : "no label"}, id=${device.deviceId}`); 75 | }); 76 | }) 77 | ``` 78 | 79 | 其中 `kind` 是輸入設備的類型,`label` 是裝置的名字,`deviceId` 是每個設備有獨一無二的 ID,等等會有更多討論。 80 | 81 | 不過,如果真的執行上述的 code,讀者可能會發現拿不到 label,如圖所示。 82 | 83 | ![](/images/mediadevice-no-labels.png) 84 | 85 | 這是因為只有當取得使用 media device 的權限(例如取得讀取麥克風的權限)時,才可以看到 label。 86 | ![](/images/mediadevice-labels.png) 87 | 88 | 相信讀者已經想到了,既然不同電腦有不同的 media devices,那就拿來做 fingerprinting 啊!這就是 media device fingerprinting 的概念:利用每個使用者都可能有不一樣的 media devices,蒐集所有 media device 的 ID 或 label,便可以生成可能是獨一無二的 fingerprint。 89 | 90 | 為了避免 media device 列表被用於 fingerprinting,標準制定者做了一些努力: 91 | 92 | 首先,device ID 並不是固定的。在不同 origin 下會有不同的 device ID,例如前面兩張圖片在同個裝置、同個瀏覽器下,有不一樣的 device ID。清除 cookie 會促使 device ID 重新生成,無痕瀏覽下每個 session 也都會有自己的 device ID,並於 session 結束時清空。因此 device ID 最多用於 same-site tracking,無法用於 cross-site tracking,而且存活長度頂多與 cookie 一樣長。不過需要注意的是,device ID 只有透過 browser 內建的方法清除儲存空間時會連帶被重置,如果用其他工具(例如一些 anti-tracking extension)清空儲存空間,device ID 不會一起被重置。 93 | 94 | 不過,即便 device ID 不固定,裝置名稱(label)仍然是固定的。於是,如同前文所述,若要取得裝置名稱,需要獲得存取權限,也就是會直接跳出一個授權框詢問使用者是否同意網站存取麥克風或鏡頭。這使得在背後偷偷做 media device fingerprinting 變得不太可能。但是,如果網站本來便需要麥克風與鏡頭的權限(例如線上會議軟體),則還是可以暗自做 media device fingerprinting。因此 media device fingerprinting 雖然罕見且有門檻,但仍然是可行的。 95 | 96 | 97 | ## 參考資料 98 | [Fingerprinting WebRTC](https://privacycheck.sec.lrz.de/active/fp_wrtc/fp_webrtc.html#fpWebRTC) 99 | [WebRTC Leak Test](https://browserleaks.com/webrtc) 100 | [真实世界中的WebRTC:STUN, TURN and signaling](https://michaelyou.github.io/2018/08/01/%E7%9C%9F%E5%AE%9E%E4%B8%96%E7%95%8C%E4%B8%AD%E7%9A%84WebRTC/) 101 | [PSA: Private IP addresses exposed by WebRTC changing to mDNS hostnames](https://groups.google.com/g/discuss-webrtc/c/6stQXi72BEU) 102 | [PSA: mDNS and .local ICE candidates are coming](https://bloggeek.me/psa-mdns-and-local-ice-candidates-are-coming/) -------------------------------------------------------------------------------- /content/docs/usages-and-categories.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 2 3 | title: "Web Tracking 的角色、用途與分類" 4 | --- 5 | 6 | # Web Tracking 的角色、用途與分類 7 | 8 | 在開始討論具體技術之前,我還想額外討論 web tracking 常見的用途以及試圖解決的問題。畢竟 web tracking 這個領域存在各種不同技術是一回事,它們為何存在、當初發明它是想解決什麼,又是另一回事。我希望藉由這篇文章去讓大家了解到 web tracking 到底想解決什麼問題,而這個問題又為何會重要,甚至是需要被解決。 9 | 10 | 從不同使用情境,我們又可以把 web tracker 做分類,不同類型的 web tracker(追蹤器)可能有不同的特性與危害。雖然這與 web tracking 技術沒有直接相關,但可以讓我們更了解 web tracking 的生態。 11 | 12 | ## Web Tracking 與廣告投放 13 | 若論 web tracking 的用途,基本上不外乎是用於廣告,在前面我們也有非常簡短地討論過兩者的關係。此處我們將稍微更仔細一點地討論 web tracking 與廣告投放的關係,以及這為何是個問題。 14 | 15 | 在傳統廣告的世界,廣告主只知道某個特定媒介有著特定類型的觀眾,例如百貨公司旁的公車站充滿了喜歡時尚潮流的人,閱讀親子雜誌的人多半是有小孩的人,每天晚上準時收看鄉土劇的人多半是年長者, 16 | 17 | 這些對於媒介與其客群的認識使廣告主可以粗略地投放廣告,例如親子雜誌上可能會有書籍與數位學習工具的廣告。然而,不同於傳統廣告分析其客群的普遍性質,數位廣告可以做到針對個人的分析,這項成就的一大背景來自於無所不在的行為追蹤與分析。 18 | 19 | 使用者若是安裝任何一個 adblocker,例如 uBlock origin、Ghostery等,並且瀏覽隨意一個網站。這些 adblocker 便會告訴使用者它攔截了多少 tracker,這些tracker 來自哪裡,以及對於一些更進階的 adblocker 而言:什麼資料被拿走了。舉例來說,當我造訪 Forbes 的網站時,可以看到非常多看 Forbes 應該沒什麼關聯的請求,這些請求都是由 tracker 發出的。 20 | 21 | 由此可見,當我們使用那些裝有 tracker 的網站時,瀏覽器不只會將資訊傳給該網站,還會將其傳給廣告商或追蹤者,畢竟瀏覽器下載一堆程式碼並執行它,而 tracking scripts 的確表明了應該把資訊傳給廣告商或追蹤者,瀏覽器只是按照指令做事。結果是,當使用者瀏覽各種看似無害的網站時,在台面下有著許多蠢蠢欲動的 tracker 正在嘗試蒐集資料,而使用者很可能完全不會感覺到它們的存在。而這些 tracker 的來源從熟悉的企業(例如 Google、Adobe)到很可能完全沒聽過的公司(例如 Forbes 上有個請求是送給一間名為 Sharethrough 的公司)。 22 | 23 | 不過,為什麼這些網站要接受他們植入 tracker 呢?網站所有者之所以同意廣告商與追蹤者植入這些 tracker,在於廣告商與追蹤者付費給網站所有者,讓他們可以在網站上插入廣告、植入 tracker。以前例來說,決定 Forbes 的網站上顯示哪些廣告的其實不完全是 Forbes 自身,而是這些付錢給 Forbes 的廣告平台(ad networks),這些廣告平台買了網站上的廣告欄位,再轉手賣給希望投放廣告的人。簡言之,這些廣告平台的工作是媒合網站上的廣告欄位與想要投放廣告的人(廣告主)。這麼做對 Forbes 的好處是,他們不用花費心力處理廣告投放,只需要把欄位給廣告平台,讓他們去處理即可。對於廣告平台而言,他們不只可以將這個廣告欄位轉賣給需要的人,還可以藉此在網站上插入 tracker。當足夠多網站都被廣告平台插入 tracker 時,他們也就有足夠多的資訊去媒合到更好的廣告主,藉此增加收益。 24 | 25 | 不只廣告平台,也有一些追蹤者只單純收集資料並轉賣這些資料。因為廣告商可能需要很多不同資料來做出好的判斷,於是有個產業稱為資料仲介(data broker)來幫忙整合這些資料。資料仲介可能會向多個追蹤者購買資料,可能他們自己同時也是追蹤者,他們蒐集到或買到資料後會為每個使用者建立 profile,把這些資料賣給需要資料的公司。 26 | 27 | 所以這些廣告商或追蹤者都在蒐集些什麼?以前面提到的 Sharethrough 為例,他們蒐集的內容包含各種 identifier、 地理位置資訊、網路流量、瀏覽過的頁面、瀏覽時間、錯誤信息、點擊的連結、點擊或檢視的廣告、廣告顯示的時間長短、系統與瀏覽器的各種資訊等。這些資訊會用於決定投放哪些廣告、用於廣告競標等。 28 | 29 | 在決定要把廣告欄位賣給哪個廣告主時,通常有幾種策略。 30 | 31 | 第一種是直接買斷,也就是廣告主直接與廣告平台談好價格,例如千次曝光(CPM)或點擊(CPC)的價格,然後廣告平台根據其對使用者的認識以及廣告主的出價投放廣告。 32 | 33 | 第二種是經過一段名為「即時競標」(real-time bidding, RTB)的過程,也就是廣告平台將廣告欄位資訊公告出來,供不同人競標。可以想像:網站要賣掉一個廣告欄位,而廣告主想買到一個廣告欄位投放廣告,於是多個廣告主便會去競標一個欄位。這個過程通常有幾個身分:Ad-exchange (ADX) 是競標的市場所在;Supply-Side Platform (SSP) 為賣方(也就是網站)服務,負責加入不同的 ad-exchange 找到出價最高者;Demand-Side Platform (DSP) 為買方(廣告主)服務,負責使用一些演算法協助從 ad-exchange 那邊競標到最划算的欄位。當使用者造訪網站時,SSP 會先蒐集使用者資訊,將廣告請求交給 ADX,接著 ADX 會將使用者資訊分享給各個 DSP,DSP 看過使用者資訊後出價競標該欄位,得標後 ADX 會再將資訊回傳給網站,網站顯示廣告並找 DSP 收錢。這整個過程可能只在幾百毫秒內由程式完成,使用者幾乎毫無感覺。 34 | 35 | 在這整個環節中,對使用者的認識非常重要。在傳統買斷的廣告投放中,如果廣告平台對使用者沒有認識,可能就會投放錯廣告而無法收到廣告點擊的收益 ,而且廣告主也可能發現投放在這個廣告平台上的廣告都比較少點擊,而不再願意使用該平台。同樣的,在 RTB 中如果 DSP 誤估了使用者的偏好與消費能力等,可能就會開出不合理的價格。上面的討論省略了非常多細節(例如 ADX 不一定只會做 RTB)1,但此處的重點在於呈現為何廣告商會對使用者資料感興趣。 36 | 37 | ## Web Tracking 的角色 38 | 在 web tracking 的資料中,大概可以分成四個角色:使用者、網站管理員、追蹤者、資料使用者。 39 | 40 | 使用者是資料的來源,他們在網站上的使用行為會被記錄下來。網站管理員就是網站管理員(?),通常他們會和追蹤者合作,在網站中埋入追蹤者的 tracker,讓使用者在他的瀏覽器中執行 web tracking 的 code。網站管理員可能基於許多誘因與追蹤者合作,例如藉此使用追蹤者提供的服務(e.g. Facebook 的 Like 按鈕、Google Analytics),或是有經濟誘因。追蹤者則是主要開發與部屬 tracker 的單位,他們蒐集到資料後,會賣給或分享給資料使用者,也就是最後需要用這些資料的單位。資料使用者則會利用這些資料去投放廣告或提供服務。在許多情境中,資料使用者和追蹤者可能是同一個單位,一方面蒐集資料一方面使用資料,像是 Facebook。甚至可能資料使用者就是網站管理員自己,藉由這些追蹤者從網站上拿到的資料,網站管理員可以更加了解使用者如何操作自己的網站,網站可以如何改善。 41 | 42 | ![](/images/tracking-party.png) 43 | 另外,因為分析者可能需要很多不同來源的資料,希望有人可以幫忙 aggregate 好整包一起賣,於是有個產業叫做 data broker(資料仲介)。Data broker 可能會跟追蹤者買資料,可能他們自己就同時也是追蹤者,他們蒐集到或買到資料後會為每個使用者建立 profile,把這些資料賣給需要資料的人,像是廣告商。 44 | 45 | 這個故事做了許多簡化。在真正的廣告產業中,還有非常多不同單位,像是有負責整理廣告 demand 的,有負責處理廣告欄位 supply 的,也幫忙做 bidding(廣告欄位競標)的。[廣告產業的複雜程度](https://lumapartners.com/content/lumascapes/display-ad-tech-lumascape/)簡直是場災難,我只挑出了與 tracking 密切相關的角色,其餘的就被我簡化掉了。 46 | 47 | ## Web Tracking 的用途 48 | ### 廣告投放 49 | 如同前面一再提及,web tracking 最常見與投放廣告相關。Web tracking 最早便是為了投放廣告而出現的。藉由追蹤使用者造訪過哪些網頁,推測使用者可能對什麼有興趣,藉此投放廣告。至今 web tracking 最常見的用途仍然是為了蒐集資料做廣告投放,在可預見的未來大概也不會改變。 50 | 51 | 當今整個廣告產業有很大一部份著重在精準投放,甚至可以說,如果有一天精準投遞消失了,廣告產業雖然不會消失,但肯定和現在很不一樣。舉個簡單的例子,Apple 之前在 iOS 14.5 加上 App Tracking Transparency 功能,就讓 Facebook 跑出來[哀號](https://www.cnbc.com/2022/02/02/facebook-says-apple-ios-privacy-change-will-cost-10-billion-this-year.html)說他們會損失 10B 的利潤。題外話亂扯個,這個案例比起說是在講 tracking 對廣告產業多重要,更重要的可能是 Apple 的壟斷地位到底有多恐怖,一個小功能可以造成這麼大的影響。 52 | 53 | ### 聯盟行銷(Affliate marketing) 54 | 另一個同樣與廣告有關的是聯盟行銷,或有時稱為夥伴計畫(Affiliate / Referral Program)等,其運作方式是,如果使用者可能會在網站 A 上看到某個商品的宣傳,於是他從網站 A 藉由點擊連結或任何其他方式轉跳到某個電商 B,則當使用者在電商 B 消費時,網站 A 可能會獲得分潤之類的。此時電商 B 需要有個方法使其知道使用者是從網站 A 來的,web tracking 在此便可派上用場。 55 | 56 | ### 個人化推薦 57 | 類似於投放廣告,個人化推薦為透過分析使用者瀏覽網頁的行為來找到使用者的偏好並藉此推薦文章、影片、商品、搜尋紀錄等。舉例來說,如果 Facebook 發現我在瀏覽偏好某個政黨的新聞,他可能就會想推薦給我跟支持此政黨有關的貼文等。 58 | 59 | ### 網站分析(Web Analytics) 60 | 對於網站擁有者而言,使用者進入這個網站之前與之後發生了什麼事是很重要的,例如流量來源、逗留時間、轉換率等,都是重要的資訊。為了讓使用者在網站上的行為可以被連貫地分析,例如找到重複瀏覽者等,必須使用 web tracking 技術來輔助。 61 | 62 | 另外一個與之類似的是 usability tests,在於分析網站的可用性,例如追蹤使用者的滑鼠移動與點擊,藉此了解使用者究竟是如何使用這個網站,他們的使用方式是否符合期待,或是可能網站設計無法使他們正確了解如何操作等。 63 | 64 | 許多網站會做 A/B test,此時 web analytics 與 usability tests 便至關重要,可以提供許多有用的資訊,協助網站管理員做出好的設計決策。 65 | 66 | Web analytics 與 usability tests 雖然會蒐集許多資訊,但可能是無害的,因為這些東西通常只會被用於內部分析,而且也不太會去做跨網站之間的分析與資訊共享。然而這不意味著 web analytics 與 usability tests 就沒有隱私侵害,並不是每個人都想被如此分析,使用者在網頁上的操作也可能洩漏一些預料之外的資訊,例如操作習慣。 67 | 68 | ### 監控 69 | 無論對於政府、司法機構或數據分析相關公司而言,使用者瀏覽什麼資料都是很珍貴的,可以被拿來分析的。 70 | 71 | 我們已經有[非常多政府與司法機構嘗試監控人民的案例](https://zh.wikipedia.org/zh-tw/%E6%94%BF%E5%BA%9C%E7%9B%A3%E8%81%BD%E9%A0%85%E7%9B%AE%E5%88%97%E8%A1%A8)了,其中以美國 NSA 的 PRISM 最為知名。這裡我無意討論監控的正當性或合法性,僅是指出政府與司法機構有動機與案例監控網路使用。 72 | 73 | 數據分析相關公司,尤其資料仲介(data broker),對使用者瀏覽網站以及操作方式等也同樣感興趣,因為這些資訊可以被賣給廣告商或需要這些資料的單位。例如保險公司如果知道這個使用者常常瀏覽競速相關資訊,可能就會想提升車禍險的保費;電商如果知道使用者常常瀏覽筆電開箱文,可能會把筆電價格拉高一點。其中最有名的案件莫過於[劍橋分析事件](https://en.wikipedia.org/wiki/Facebook%E2%80%93Cambridge_Analytica_data_scandal)。總之,數據就是資訊世界的貨幣。 74 | 75 | 76 | ## Web Tracker 的分類 77 | 在上一段,我們簡介了幾個 web tracking 的用途,所以就想稍微延伸一下,介紹一個我私心覺得蠻不錯的 web tracker 分類。DuckDuckGo 的 [Tracker Radar](https://github.com/duckduckgo/tracker-radar) 是一個分析 tracker 的專案,其中他們提出了一個對於 tracker 的分類。 78 | 79 | 原文[在此](https://github.com/duckduckgo/tracker-radar/blob/main/docs/CATEGORIES.md),以下內容有些是翻譯自該文件並加上我自己的附註,不過只會介紹幾個我認為需要知道的。 80 | 81 | - Action Pixels:類似於 [Web beacon](https://en.wikipedia.org/wiki/Web_beacon),用於追蹤發生在 first party 或 third party 的特定事件。 82 | - Ad Motivated Tracking 與 Advertising:為了蒐集資料來投放廣告而部屬的 tracker。 83 | - Analytics:用於網站分析。 84 | - Audience Measurement:更進一步的網站分析,通常會包含一些 demographic 的資料(性別、種族等)以及行為分析。 85 | - Session Replay:這類型的 tracker 會追蹤使用者在網站上的所有行為,像是滑鼠移動、點擊、捲動或甚至網路流量,但它們本質上還是網站分析。 86 | - Federated Login, Online Payment, Single Sign On, Social Network 等:顧名思義,就是與 Federated Login, Online Payment, Single Sign On 等有關的 tracker,這些服務為了做產品使用分析與防止詐欺,通常會部屬一些 tracker 87 | 88 | 89 | 至此我們已經完成對 web tracking 的 high-level overview。我知道這篇文章蠻無聊的,但有這些背景知識,接下來就可以走向較為技術細節的部份了! -------------------------------------------------------------------------------- /content/docs/browser-fingerprinting/defense.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: "Browser Fingerprinting 的防禦" 4 | --- 5 | 6 | # Browser Fingerprinting 的防禦 7 | 在過去幾個章節中,每次介紹完 browser fingerprinting 的攻擊手段,都多少有提到一些防禦手段,所以這篇文不會著重在對於特定 fingerprinting 技術的防禦,而是對於防禦的整體策略分析,也就是防禦策略的大方向,並討論這些方向可能遭遇的問題,為何成功或失敗。如果讀者對特定 browser fingerprinting 技術的防禦有興趣,仍然應該回去檢視前面的章節。 8 | 9 | ## 為何 Anti-Fingerprinting 這麼難? 10 | 首先我們可能得先問,為什麼「有效的防禦」這麼難?或是說,anti-fingerprinting 可以如何搞砸? 11 | 12 | 試想,有個 tracker 用 `screen.width` 與 `screen.height` 做 fingerprinting,我的螢幕是 1920x1200,這個大小不算特別常見,為了避免被 browser fingerprinting,我決定改變 screen 的數值,於是我隨便選一個數值,像是 1234x5678。這下 tracker 拿不到我的真實螢幕大小了,它只會拿到我造假的資訊。這是個有效的防禦嗎? 13 | 14 | 過去我們討論過 surprisal 與 anonymity set 的概念,那時有提到一個很重要的觀念:一個 fingerprint 要有效,它必須足夠罕見,與足夠少人重複,也就是 surprisal 夠大,anonymity set 夠小。試問 1234x5678 比較獨特,還是 1920x1200 比較獨特?顯然是前者吧。Browser fingerprint 的重點不在於正確或是合理,而是足夠獨特。就算是錯的,只要錯得很獨特,一樣可以拿來做 fingerprinting。 15 | 16 | 既然不能隨便造假一個數值,那如果偽造成最多人使用的 1920x1080,會比較好嗎?的確我們躲在更大的 anonymity set 中,但很多正常功能也都會用 `screen`,像是用於網頁排版,於是這些網站也就跟著一起壞掉了。 17 | 18 | 既然固定一個數值會使一些功能無法正常運作,那如果每次都隨機生成一個假的數值,如此則每次 fingerprint 都不同,即便獨特也不持久,然後只要隨機的值不要離真的值太遠,就不會跑版了。乍看之下這個方法好像可行,但 tracker 只要重複取樣很多次並計算平均值,便可以還原出真正的數值。 19 | 20 | 假設我們找到一個方法可以讓改 screen 的同時不破壞既有功能,也不必然可以高枕無憂。例如,我宣稱自己是 1920x1080 結果 user-agent 說這是一台手機,手機好像通常不會有 1920x1080 這種大小;或我宣稱自己是 1920x1080 結果滑鼠座標可以移動到超過 1080 的地方。這些都透露了我正在騙人。更何況,幾年後最主流的螢幕大小很可能不是 1920x1080 了。 21 | 22 | 這不是一個完整的列表,但至少至此我們已經可以看到 anti-fingerprinting 並不是個容易的問題。 23 | 24 | ## Browser Fingerprinting 的防禦手段 25 | 26 | 接下來我們來討論幾個 anti-fingerprinting 的策略。值得注意的是,這些策略的分類並非涇渭分明,有些方法可能同時使用多個策略。 27 | 28 | ### 方向一:混淆 Browser Fingerprint 來源的值 29 | 第一種,也是最常見也最容易想到的方式是,直接去改 browser fingerprinting 來源的值,如此則 tracker 拿不到真正的 browser fingerprint,可能是大家的 fingerprint 都一樣,無法分辨,也可能是每次 fingerprint 結果都不一樣,無法做有效的長期追蹤。例如前面舉例的修改 `screen` 成 1920x1080,討論 canvas fingerprinting 時討論的增加 noise 等,都屬於這類。 30 | 可以進一步把這一類型的防禦再區分成兩種策略。 31 | 32 | 第一種是直接改數值,可能是改成預設的一個數值,例如大家的 `screen` 都改成 1920x1080,或是隨機產生一個值,例如隨機生成 `screen` 數值,藉此掩蓋真實的數值,讓使用者隱藏在一大群 anonymity set 裡面。前面提過的 `screen`、修改 user agent 等,都屬於這類。其中最早的嘗試之一是 PriVaricator。為了避免破壞使用者體驗,他們設計了一系列的 policy,只有在明顯是在做 fingerprinting 時才會啟動防禦。此舉雖然可能被發現,但就算被發現了,也難以使 fingerprint 持續存活。 33 | 34 | 這種策略會遇到幾個挑戰。首先,如果同一個資訊可以從多個來源取得,假設有其中一個來源沒有被正確地修改,便會被發現 fingerprinting 結果有誤,例如從 HTTP Request Header 中的 user agent 與 JavaScript 的 `navigator` 物件都可以取得作業系統的資訊,如果前者揭示的是 macOS,後者卻自稱是 Windows,便可發現其中有詐。此外,如果資訊變得太罕見而詭異,可能會顯得獨特,本身就是一個好的 fingerprint,也可能和其他現有資訊衝突(例如 macOS 上的 browser 宣稱自己的顯示卡品牌是高通),也可以藉此發現內容是偽造的。最後,直接改數值往往可能嚴重破壞使用者體驗,畢竟除了做 browser fingerprinting 以外,這些 API 往往有更常見、正常的用途。例如 Firefox 的 Fingerprint Protection,其在許多 API 都會給出假的結果,像是 timezone 全部回報 UTC,這雖然可以感受隱私,但將嚴重影響使用者體驗,使用者將不再能享受網頁能自動調整到正確時區的方便。 35 | 36 | 第二種策略是增加雜訊,其雜訊小到一般使用者不會注意到,但足以讓 tracker 拿到充滿雜訊的資料而無法有效利用。這雖然可能使 fingerprint 變得獨特,但卻會使 fingerprint 無法延續。這類型的方法主要用於對抗 canvas fingerprint(在 canvas 加上雜訊)與 audio fingerprinting(在 AudioContext 產出的波形加雜訊)。 37 | 38 | 這類型的方法可能會遇到數個困難。首先,如果每次雜訊都不一樣,多取幾次取個平均便可以還原出真實的數值了。因此,目前多數方法都會有固定的雜訊,可能在同個 eTLD+1 有同樣的雜訊,如此則 browser fingerprinting 只能用於 same-site tracking,也可能是每次開啟新的 session(例如重開 browser、清空 cookie 等)就重新生成 noise,如此則 browser fingerprint 最多只能存活到 session 結束,沒辦法活得比 cookie 還久。然而,除了雜訊本身要夠亂以外,還必須確保其不能被還原。以 canvas fingerprinting 來說,如果直接將雜訊以相加的方式干擾 canvas fingerprinting,只要畫出空白畫布,就可以還原出雜訊,把 canvas 繪製結果減去雜訊就還原出真正的 canvas 了,所以擾動方式肯定不會是直接把繪製結果與雜訊相加。 39 | 40 | 目前最有影響力的概念來自 FPRandom。在 canvas fingerprinting 防禦上,為了避免上述問題,FPRandom 選擇不在最終結果上做擾動,而是在繪圖時就直接做擾動,例如當指定要畫橘色時,直接把整個色塊做些微的調整,且這些擾動在同個 session 時都是固定的,如此則確保 tracker 沒有辦法做很多次 canvas 來取平均,且也不容易取出雜訊。Brave 的作法則是,加上雜訊時會每個 eTLD+1 有自己的雜訊且加入雜訊的方法會依賴於 canvas 繪製的原始內容,使得雜訊在同一張 canvas 繪製結果上是固定的,但在不同繪製結果上是不一樣的,讓提取雜訊變得困難或不可能。 41 | 42 | 另外,雖說增加的雜訊可以小到讓使用者無法注意到,但其實有另一個方向是:讓 JavaScript 讀到的與 browser 繪製出來的資料不一樣。例如一張 canvas,可能 browser render 出來的是原本的,JavaScript 藉由 toDataURL 拿到的是有擾動過的,如此可以進一步降低 anti-fingerprinting 對使用者的影響。 43 | 44 | ### 方向二:使大家的 Fingerprint 都一樣 45 | 另一個方向是,讓大家的 fingerprint 都一樣,如此則有超大的 anonymity set,大家都會隱藏在人海之中。 46 | 47 | 其中做得最好的可能是 Tor Browser。Tor Browser 開發團隊對 Firefox 做了很多改善,甚至讓視窗統一都是 800x600,藉此讓所有 Tor Browser 的 fingerprint 都是一樣的。另一個是 iOS 上的 Safari 有著一樣的 canvas fingerprint,這也是可以理解的,畢竟所有同世代的 iDevice 都有幾乎一樣的硬體,然後 OS 也都是 iOS。 48 | 49 | 然而這類型的方法有個缺點是,這種「大家都一樣」的 fingerprint 有時可以是有意義的,例如 Tor Browser 的特徵太過於明顯,和其他一般 browser 都不太一樣,但同時「使用 Tor Browser」這件事情有相當程度的意義(畢竟一般使用者不會用 Tor Browser),於是這種大家都一樣的 fingerprint 反而讓網站可以偵測出使用者正在使用 Tor Browser。 50 | 51 | 此外,如果 fingerprint 都一樣,一旦有一個環節出了錯,fingerprint 就會特別突出。例如 Tor Browser 大家都有一樣的 fingerprint,但如果有個使用者 resize 了他的視窗,從 800x600 變成 1920x1080,則這個使用者會突然變得格外獨特,竟然有某個 Tor Browser 使用者有著與眾不同的視窗大小。相較於 Chrome 或 Firefox 大家本來 fingerprint 都很不一樣,視窗大小也很多元,沒什麼好訝異的,在 Tor Browser 的 fingerprint 之中,視窗大小不一樣是非常重要的資訊。 52 | 53 | 另一個同樣屬於這個策略的是我們在 WebGL Canvas Fingerprinting 中提過,其差異來自於浮點數運算的誤差,只要讓大家的浮點數運算過程都一樣,就會產生一模一樣的 WebGL Canvas Fingerprint。 54 | 55 | ### 方向三:移除或限制用於 Fingerprinting 的 API 56 | 最終極的策略是,直接把用於 fingerprinting 的 API 拿掉或是限制其使用。 57 | 58 | 有些 API 本身可能很少用,卻可以用於 fingerprinting,既然大家都用不太到,不如就拿掉吧。例如 Battery Status API 真的很少被用到,很可能根本沒幾個網站有真的在有意義的使用它,所以 Firefox 就決定直接把 Battery Status API 拿掉了,Chrome 則是限制可以拿到的資料量多寡,電量只能拿到小數點後兩位,畢竟對於正常用途來說,知道小數點後兩位就綽綽有餘了。 59 | 60 | Tor Browser 則是限制許多 API 的使用,需要取得使用者的同意才得以執行,例如是 canvas 與 WebAudio。畢竟對於多數網站來說,就算沒有 canvas 或 WebAudio也不至於不能用,但這些功能也可能有正當用途,沒必要也不適合直接拿掉,不如就預設封鎖,要求使用者積極地同意使用。 61 | 62 | 前面提過的 Firefox 的 Fingerprinting Protection 也有包含限制 API 的部份,像是 WebSpeech、Performance API 都會被停用,然後 canvas 在呼叫 toDataURL 時需要得到使用者同意。 63 | 64 | 但無論是直接移除,或是限制 API 使用,對使用者體驗來說都還是有害。其實我們沒必要要求一點資訊都不能洩漏,只需要確保洩漏的資訊量沒有大到可以做有效的 browser fingerprinting 即可,這正是 Google 的 Privacy Budget 的概念:tracker 只有多少「預算」可以存取危險的 API,一旦預算用完,就不能繼續存取這些 API 了,藉此限制 tracker 可以獲得的資訊量。我們後續會再做更多討論。 65 | 66 | ### 方向四:模擬不同環境 67 | 在方向一我們提到了 browser fingerprinting 的防禦可以藉由欺騙 tracker 來達成,然而這樣的防禦機制很容易被偵測出來。簡言之,欺騙 tracker 不難,但要騙到 tracker 不會發現自己被騙,則困難許多,太容易留下各種蛛絲馬跡使 tracker 發現瀏覽器正在提供錯誤資訊。 68 | 69 | 因此,另一個方向是,比起處心積慮騙過 tracker,不如直接讓 tracker 跑在假的環境,例如讓整個瀏覽器執行在 Virtual Machine 或是 Container 中,如此則 tracker 只能拿到虛擬環境中的參數,只要一直更換 VM 或 container,就可以一直產生出不同的 fingerprint,而且因為這些 fingerprint 在當下都是真的,所以 tracker 很難發現自己其實是跑在刻意創造出來的虛擬環境中。雖然基於顯而易見的效能考量,這種方法很難被大規模部屬。但是有許多市售宣稱可以偽造 browser fingerprinting 的工具,多半是使用這種方式建立的,畢竟在這些工具的使用場景中,效能考量與使用者體驗相對不那麼重要。 70 | 71 | ### 方向五:直接封鎖 Tracker(Content Blocking) 72 | 最後就是我們的老面孔:content blocking。不管有多少方式限制 tracker,都不如一開始不要載入 tracker。基本上就是在 [content blocking]({{< relref "/docs/stateful-tracking/content-blocking" >}}) 討論過的東西,也繼承了之前討論過的[限制]({{< relref "/docs/stateful-tracking/content-blocking-circumvention" >}}),所以就不多談了。 73 | 74 | 75 | 76 | 在這篇文章,除了數個防禦的策略以及他們可能的問題與挑戰,我們也討論了 anti-fingerprinting 可能會犯的錯誤,明天則會進一步討論這些錯誤為什麼會出現,以及其有些出乎意料的結果:失敗的 browser fingerprinting 導致使用者更容易被 fingerprint。 77 | 78 | ## 參考資料 79 | Laperdrix, Pierre, et al. "Browser fingerprinting: A survey." ACM Transactions on the Web (TWEB) 14.2 (2020): 1-33. 80 | Laperdrix, Pierre, Benoit Baudry, and Vikas Mishra. "FPRandom: Randomizing core browser objects to break advanced device fingerprinting techniques." International Symposium on Engineering Secure Software and Systems. Springer, Cham, 2017. 81 | Nikiforakis, Nick, Wouter Joosen, and Benjamin Livshits. "Privaricator: Deceiving fingerprinters with little white lies." Proceedings of the 24th International Conference on World Wide Web. 2015. 82 | https://mozilla.github.io/ppa-docs/privacy-budget.pdf -------------------------------------------------------------------------------- /content/docs/stateful-tracking/cross-site-tracking-without-cookie.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 3 3 | title: "殺死 Third-party Cookie,一樣可以做 Cross-site Tracking:CNAME Cloaking, Bounce Trackers, and Link Decorations" 4 | --- 5 | 6 | # 殺死 Third-party Cookie,一樣可以做 Cross-site Tracking:CNAME Cloaking, Bounce Trackers, and Link Decorations 7 | 先前我們介紹了 third-party cookie 如何用於 web tracking,尤其 cross-site tracking。多數瀏覽器都已經封鎖或即將封鎖 third-party cookie,更何況在許久之前便已有不少人藉由 browser extension 或其他工具阻擋 third-party cookie,這使得傳統的 cross-site tracking 技術不再是穩定可用的。為此,追蹤者也開發了數種其他 cross-site tracking 的方法。在此章節,我們將介紹三種不使用 third-party cookie的 cross-site tracking 技術,分別是 CNAME cloaking、bounce trackers 以及 link decoration。 8 | 9 | ## CNAME Cloaking 10 | ### CNAME Cloaking 是什麼 11 | CNAME 是一種 DNS record 的型態,其所表達的意思大概是「我被稱為 `tracker.example`,但其實我就是 `tracker-real.example`」。 12 | 13 | 如果有用過 GitHub Page 可能就會知道 GitHub Page 的 [custom domain](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-a-subdomain) 便是使用 CNAME 達成,使用者在他的 custom domain `example.com` 加上一筆指向 `.github.io` 的 CNAME record,於是在嘗試 resolve 時,電腦就知道 `example.com` 其實就是 `.github.io`,無論 `.github.io` 指向哪裡,總之電腦知道想要找到 `example.com`,去找 `.github.io` 肯定沒錯。 14 | 15 | 有些 tracker 利用了 CNAME 的性質,嘗試把 tracker 移動到網站自身的網域底下。例如現在有個追蹤者把 tracker 的資源放在 `tracker.tracking.example`,有個想要使用 tracker 的網站是 `news.example`,一般來說如果直接嘗試與 `tracker.tracking.example` 溝通,會被視為 third-party 而有諸多限制,例如不能傳送 cookie。不過,如果我用 CNAME record 把 `tracker.news.example` 指向 `tracker.tracking.example`,然後在網站引入 `tracker.news.example` 的資源,則瀏覽器會誤以為這是 first-party subdomain 而有比較寬鬆的限制,使用者可能也會因為那是 first-party subdomain 而放下戒心,即便那背後其實是 `tracker.tracking.example`。 16 | 17 | 稍微岔題一下,讀者可能會疑惑,`tracker.news.example` 和 `news.example` 是不同 origin,理應不能讀到對方的 cookie,所以拉到 first-party subdomain 理論上應該沒有幫助。不過 cookie 有個屬性是 `domain`,其功能就是想達成在 subdomain 之間共享 cookie。假如我在 `news.example` 設定這樣的 cookie: 18 | ``` 19 | Set-Cookie: key=value; Domain=news.example 20 | ``` 21 | 則在 `subdomain.news.example`,一樣可以讀到該 cookie。 22 | 23 | 這種藉由 CNAME 來偽裝成 first-party subdomain 的技術稱為 CNAME cloaking。 24 | 25 | ### CNAME Cloaking 的危害 26 | CNAME cloaking 的危害大概有以下幾點 27 | - 它用於繞過 ad blocker 等所使用的 filter list,就算 tracker 本身被阻擋,我也可以將其移動到自己的 subdomain。 28 | - 如果每個人都可以有自己專屬的 tracking domain,那要加入 filter list 的 tracking domain 沒完沒了。 29 | - 更糟糕的是,在(新版的)Chrome 與 Safari 對於 filter list 的規則數量有所限制,也就是能 block 的 URL 是有限的,如果 tracking domain 無限地多,很快就會撞到上限了。 30 | - 相較於網站管理員只需要幾分鐘就能改 CNAME 設定並創造新的 tracking domain(這個過程甚至可以被自動化),filter list maintainer 可能需要花很長一段時間才會意識到出現新的 tracking domain,且需要時刻注意 tracking domain 會一直改,這大幅增加了維護 filter list 的成本。 31 | - Browser 中有一些機制會因為是 first-party subdomain 而有較為寬鬆的限制,例如前述的共享 cookie,這使得放在 first-party subdomain 的 tracker 可以蒐集到更多資訊。 32 | - 使用者與 filter list maintainer 可能因為 tracking domain 是 first-party subdomain 而放下戒心,沒意識到這是危險的。 33 | 34 | CNAME cloaking 目前被非常廣泛地使用,例如追蹤公司 Criteo 擁有一個 domain `dnsdelegation.io`,可以看看有多少[根本不屬於 Criteo 的 domain 用 CNAME 指向他們的網域](https://securitytrails.com/list/cname/dnsdelegation.io)。 35 | 36 | ### CNAME Cloaking 的解法 37 | CNAME cloaking 從[第一次](https://medium.com/nextdns/cname-cloaking-the-dangerous-disguise-of-third-party-trackers-195205dc522a)被 security community 發現至今也才不過短短三年,因為其重要性、普遍程度以及影響力,已經吸引許多研究者做深入的探究,也促使各個 anti-tracking 的工具提出相對應的解方。 38 | 39 | 解決 CNAME cloaking 有個看似簡單的解法:讓 content blocker 去檢驗每個 domain 的 DNS resolve 出來有沒有指向已經在 filter list 裡面的 tracking domain。然而問題是,多數的 browser 都沒有給 extension 查詢 DNS 的權限,唯一的例外是 [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/dns),所以目前 Firefox 上的 uBlock Origin 已經可以抵禦 CNAME cloaking。另一個作法是直接把防禦機制放在 browser 內部,就可以有足夠的權限看到 DNS query 了,這就是目前 Safari 的作法,即我們以後會介紹的 ITP。 40 | 41 | ## Bounce Trackers 42 | Bounce Tracking 是一種非常常見的 web tracking 技術,其特色在於,所有 tracking 都是在 first party 完成的,就算沒 third-party cookie 一樣能運作。 43 | 44 | 當使用者在 `blog.example` 上點擊一個連向 `shop.example` 的連結,理應會直接把使用者帶到 `shop.example`,但 bounce tracker 會很快速的把使用者依序帶到 `tracker1.example`, `tracker2.example`, `tracker3.example`, ...,並在每次轉跳時都帶上目的地的 URL,最後才轉到真正的目的地(`shop.example`)。當使用者被轉到 `tracker1.example` 時,`tracker1.example` 知道了這個使用者嘗試造訪 `shop.example`,而且因為 `tracker1.example` 的 cookie 在此時是 first-party cookie,所以 `tracker1.example` 可以輕鬆讀到自己 cookie 內的 identifier。以此類推,`tracker2.example` 與 `tracker3.example` 也都可以知道使用者要造訪 `shop.example` 並且知道使用者在自己手上的 identifier。 45 | 46 | 當同個使用者在 `another-blog.example` 上又點擊了 `social.example` 的連結,一樣一開始會把使用者依序帶到 `tracker1.example`, `tracker2.example`, `tracker3.example`, ...,最後才轉到目的地。於是 `tracker1.example` 因為可以讀到 identifier,於是知道這個使用者不只之前造訪過 `shop.example`,現在還準備造訪 `social.example`。 47 | 48 | 一個很常見的例子是,讀者可能也有發現過,在 Facebook 或許多其他網站上,當使用者點擊連結時,並不是直接連結過去,而是先連到類似 `social.example/link?url=...` 的地方,再轉跳出去,這就是一種 bounce tracker。 49 | 50 | ![](/images/bounce-tracker.png) 51 | 52 | 目前針對 Bounce Trackers 有最多討論的是 Apple's ITP 與 Brave。ITP 的方法會於幾天後有專文討論。Brave 則是提供一個很酷的功能是 "[debouncing](https://brave.com/privacy-updates/11-debouncing/)",它會去猜使用者真正的目的地為何(通常都直接夾帶於 URL parameter 裡面),直接把使用者帶過去,跳過中間一堆 bounce tracker。 53 | 54 | ## Link Decorations 55 | Link Decorations 與 Bounce Trackers 類似,都是讓 tracker 可以在不使用 third-party cookie 下拿到 identifier。 56 | 57 | 當使用者在某個社群平台 `social.example` 點擊連結時,`social.example` 可能會希望使用者離開自己網站後仍然可以被它追蹤,於是就在連結後面加上一個 URL parameter,例如把 `blog.example/post/123` 改成 `blog.example/post/123?click_id=foobar`,而 `blog.example` 上可能又有 `social.example` 的 script(例如留言板、分享按鈕等),於是 `social.example` 的 script 在回傳使用者行為時,就可以夾帶 `click_id`,而後端因為知道 `click_id=foobar` 是誰,於是 `social.example` 就可以了解使用者在 `blog.example` 的行為了。 58 | 59 | ![](/images/link-decoration.png) 60 | 61 | 使用者可能注意過,在 Facebook 上連出去的文章很多都帶有神秘的 URL parameter `fbclid` ,這就是 link decoration,如果目標網站有引入 Facebook 的 script(像是按讚、分享按鈕),Facebook 就可以在該網站繼續追蹤使用者的行為,並且 Facebook 可以知道這個使用者是誰。其他常見的 link decoration 所使用的 URL parameter 還包含 Google Analytics 的 `utm_*` 之類的。 62 | 63 | 使用 Chrome 或 Firefox Desktop 的人,可以使用 [ClearURL](https://clearurls.xyz) 來移除這些用於 tracking 的 URL parameter。 64 | 65 | 除了直接儲存於目標 URL 以外,另一個方法是用 HTTP Referrer 來帶資料。HTTP referrer 是一個夾帶於 request header 的資訊,用於表達「請求從哪個 URL 來的」。例如我在 `https://blog.example/post/1` 點擊 `https://shop.example`,則對後者的請求會夾帶 `Referrer: https://blog.example/post/1`。除了 HTTP request header 以外,也可以用 Javascript 的 `document.referrer` 取得 referrer。Referrer 也可以被用於 link decoration,如果使用者在 `social.example` 上點擊連結,他可能先被帶到 `https://social.example/link?url=blog.example...&click_id=foobar`,然後 `social.example/link` 再把使用者帶到 `blog.example`。當使用者抵達時,`social.example` 在 `blog.example` 上的 tracker 便可以利用 `document.referrer` 拿到其 referrer 是 `https://social.example/link?url=blog.example...&click_id=foobar`,並還原出 `foobar`。使用者不會在 URL 上看到怪怪的 parameter,也可能沒注意到中間多了一層轉跳,但 tracker 已經拿到 identifier 了。 66 | 67 | 題外話,其實 Bounce Tracking 與 Link Decortation 這一系列基於 navigation 的追蹤方法,有個稍微學術一點的詞彙叫做 "navigation based tracking"。 68 | 69 | ## 參考資料 70 | [CNAME Cloaking, the dangerous disguise of third-party trackers](https://medium.com/nextdns/cname-cloaking-the-dangerous-disguise-of-third-party-trackers-195205dc522a) 71 | [Fighting CNAME Trickery](https://brave.com/privacy-updates/6-cname-trickery/) 72 | [Intelligent Tracking Prevention 2.0](https://webkit.org/blog/8311/intelligent-tracking-prevention-2-0/) 73 | [Intelligent Tracking Prevention 2.2](https://webkit.org/blog/8828/intelligent-tracking-prevention-2-2/) -------------------------------------------------------------------------------- /content/docs/stateful-tracking/content-blocking.md: -------------------------------------------------------------------------------- 1 | --- 2 | weight: 4 3 | title: "如何對付傳統追蹤技術 (I):Content Blocking (Ad Blocking)" 4 | --- 5 | 6 | # 如何對付傳統追蹤技術 (I):Content Blocking (Ad Blocking) 7 | 在所有反制 web tracking 的方法中,最古早也最普遍被使用的方法是 content blocking。正如其名,content blocking 的邏輯很簡單,就是直接把 tracker 阻擋掉。假設現在 `example.com/tracker.js` 是個用於 web tracking 的 script,它會偷偷把使用者的瀏覽紀錄傳回 `example.com`,我們當然可以用各種方式(像是阻擋 third-party cookie)讓它沒辦法正確地辨識出我是誰,但有個更簡單的方式是:一開始不要讓 `example.com/tracker.js` 載入,不就沒問題了?這就是 content blocking 的核心精神。 8 | 9 | Content blocking 聽起來像是一個很複雜的技術名詞,但有個與之密切相關且大家都聽過的東西叫做 "ad blocker",現在主流的 ad blocker 幾乎都是藉由 content blocking 達到,也就是直接把取得廣告用的 Javascript 或其他資源(e.g. 圖片、影片等)的 request 阻擋掉。 10 | 11 | 在此文我不會嚴格區分 content blocker 與 ad blocker,因為兩者非常類似,很難界定。例如,幾乎所有的知名 ad blocker 同時都會阻擋 tracker,即便 tracker 並沒有在投放廣告。為了避免讀者誤以為 ad blocker 只是在阻擋可見的廣告,我將使用 content blocker 來稱呼這一系列的工具。 12 | 13 | ## Content Blocker 的不同阻擋樣態 14 | 如同前文所述,content blocker 的運作通常是直接阻擋廣告或 tracker 資源的請求。然而在許多情境下,直接阻擋請求不一定可行,或有時是不必要的,因此有更彈性的作法。此段將介紹三種 content blocker 阻擋廣告或是 tracker 的策略。 15 | 16 | 第一種,也是最常見的是,直接阻擋相關資源的請求。這通常是最理想的,因為不消耗無謂的網路流量,不會下載與執行可能造成隱私或資安風險的 script,當然惱人的廣告也完全不會顯示。我們通常稱此為 resource blocking。 17 | 18 | 第二種策略是隱藏特定的 element,通常稱之為 element hiding。這通常用於阻擋可見的廣告。當封鎖請求不太可行,會造成其他困擾時,雖然我們無法阻止瀏覽器載入 script 或其他資源,但至少可以避免其內容被顯示出來。Element hiding 通常是藉由插入特定的 CSS code 來隱藏(例如 `display: none`),或是直接把該 element 從 DOM tree 移除(例如 `removeChild`)。 19 | 20 | 第三種策略則主要是針對 tracker,阻止 tracking script 存取特定功能,例如使用 cookie。有時直接封鎖 tracker 可能會造成網站功能壞掉。例如,如果有個影片播放器的程式內建 tracker,直接把整個影片播放器封鎖大概不是個好方法,但如果限制它不能使用 cookie,將至少可以避免基於 cookie 的 web tracking。使用此方法最知名的 content blocker 是 Privacy Badger。 21 | 22 | ## Content Blocker 如何阻擋 requests 23 | Content blocker 最核心的功能還是在於阻擋不期待的 HTTP requests,此段將討論 content blocker 可以如何阻擋。主流 ad blocker 大致可以分成兩類:browser extension 與 network firewall。 24 | 25 | Browser extension 的 content blocker 包含常見的:[Adblock Plus](https://adblockplus.org/), [uBlock origin](https://ublockorigin.com/), [AdGuard](https://adguard.com/en/welcome.html), [Privacy Badger](https://privacybadger.org/) 等。這類型的 content blocker 是利用 browser extension 來達成的。 26 | 27 | 在不同瀏覽器有不同的達成方法,以下簡介幾個: 28 | - Chrome 與 Chromium-based browser 有兩種方式可以攔截 requests。早期多半是用 [webRequest](https://developer.chrome.com/docs/extensions/reference/webRequest/),extension 可以攔截所有 request,每一個都看看是不是應該被阻擋的內容,但這樣似乎給 extension 太大的權力了,如果 content blocker 是惡意的,所有請求會被攻擊者看到。第二種策略,也是未來 Google 打算採取的策略是 [declarativeNetRequest](https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/),事先向 browser 註冊哪些 request 應該被攔截,如果匹配到了才會轉給 extension 處理,同時前述的 webRequest 也打算移除了。不過這個方法因為缺乏彈性且可能使現存有些 ad blocker 的功能無法延續,所以有引起一些爭議。 29 | - Firefox 所使用的 extension API 和 chrome 非常雷同,不過因為 Mozilla 對 declarativeNetRequest 很[有意見](https://blog.mozilla.org/addons/2022/05/18/manifest-v3-in-firefox-recap-next-steps/),所以雖然支援它,但保留了 webRequest。 30 | - Safari 的特色是,content blocker 要預先要給 Safari 一長串應該阻擋的 requests,Safari 看到時就會直接阻擋,不會讓 content blocker 看到請求,所以可以避免惡意的 content blocker 取得使用者的瀏覽資料。但相對應的就是,Safari 的 content blocker 非常欠缺彈性。 31 | 32 | 第二種策略是 network firewall 的阻擋,其中最知名的是 [Pi-hole](https://pi-hole.net/)。其原理是,在遇到與廣告有關的 domain 時,攔截 DNS query 使其無法 resolve。這類型方法的缺點在於很難做到細微的調整,例如在 A 網站要阻擋 `example.com/tracker.js`,但在 B 網站不阻擋,或是做到阻擋 `example.com/tracker.js` 但不阻擋 `example.com/logo.png`。因此這類型的 content blocker 有式微的傾向。 33 | 34 | 35 | ## 如何偵測廣告與 tracker 36 | 至此我們還沒討論到最關鍵的問題:如何正確地偵測哪些請求是正常的,哪些是廣告或 tracker。目前有三種主流方法。 37 | 38 | 第一個,也是最常見的,是基於 filter list(黑名單)的 content blocking。這種方法仰賴人為地或自動地建立與維護廣告與 tracker 的黑名單。目前主流的幾個 filter list 都是開源社群的夥伴們共同維護。在分類上,有普遍的黑名單,也有針對區域、性質等不同類型的黑名單,例如針對 CryptoMiner 的黑名單。 39 | 40 | 幾乎所有設計給 browser extension 的主流 filter 都使用 [AdBlock Plus syntax](https://help.eyeo.com/en/adblockplus/how-to-write-filters),雖然一開始是 AdBlock Plus 在用,但之後已經成為實質上的標準了。在這個標準下,黑名單是一個文字檔,每一行都代表一個不同的 filter,可以是 domain 或 URL path(resource blocking),也可以是 CSS selector(element hiding)。 41 | 42 | ``` 43 | # 封鎖所有 example.com 作為 third-party 時的請求 44 | ||example.com^$third-party 45 | 46 | # 封鎖 example.net/ads 開頭的 URL 但不封鎖其他 URL 47 | ||example.net/ads 48 | 49 | # 隱藏所有 class 為 s-ads 的 element 50 | ##.s-ads 51 | ``` 52 | 53 | 幾個常見的為 browser extension 設計的 filter list 包含:[EasyList 系列](https://easylist.to/)(包含針對 tracker 的 EasyPrivacy 以及針對不同語系的 EasyList China, EasyList Germany 等)、[AdGuard 系列](https://kb.adguard.com/en/general/adguard-ad-filters)等。另外也有針對 Network firewall 的 filter list,例如 [Peter Lowe’s Ad and tracking server list](https://pgl.yoyo.org/adservers/),不過基本上針對 network firewall 的 filter list 稍做轉換也都可以用於 browser extension。 54 | 55 | ![](/images/ublock-origin-filter-lists.png) 56 | 57 | 第二種方法是 heuristic-based 的,也就是基於一個演算法去自動判斷一個資源有沒有可能用於 tracking 或是投放廣告。其中最知名的大概是 Privacy Badger,有興趣的人可以看看他的[演算法](https://github.com/EFForg/privacybadger/blob/master/doc/DESIGN-AND-ROADMAP.md)如何運作。簡言之,Privacy Badger 會去判斷 third-party origin 有沒有設定 cookie、有沒有在存取可能是在做 browser fingerprinting 的 API、有沒有對外傳送 first-party cookie 等,如果看起來太可疑,就推測它是 tracker 並阻擋其存取 cookie 或是完全阻擋。 58 | 59 | 第三種方法是 Machine-Learning-based 的 content blocking,也就是用 ML 去判斷一個內容是否看起來像是廣告或 tracker。目前這方面主要都還僅限於學術研究中,並沒有被很廣泛的使用,所以就不多加著墨。 60 | 61 | 往後對 content blocking 的討論,只要沒有明說,基本上都是在說 filter-list-based 的 content blocking,因為這是目前最廣泛被使用的方法。 62 | 63 | ## 如何不把網站搞壞 64 | 讓 tracking script 不要被載入,乍聽之下很簡單,但有時一段 code 不必然只用於 tracking,還可能有其他功能,例如前面提過的,有些影片播放器的程式內建 tracking 功能,或有時一個 tracking script 也可能有一些 API 供網站管理員使用,如果貿然地擋掉 tracker 可能會讓網站的功能壞掉。 65 | 66 | ```js 67 | 68 |