大開眼界:CSS指紋

語言: CN / TW / HK

大家好,我是 零一 。之前我發過一篇文章,是關於瀏覽器指紋的: 瀏覽器隱身模式下的你,仍然沒有任何隱私 ,裡面介紹了各種各樣的指紋生成方式,今天討論另一個比較新奇的思路: CSS指紋

什麼是CSS指紋?

CSS 指紋 是一種跟蹤和收集使用者資訊的技術,這種方法主要是利用了 CSS 的一些特性來跟蹤使用者的瀏覽器和裝置的各種特徵,這些特徵以後可以用來識別或跟蹤使用者

CSS指紋如何生成

原理比較簡單,主要就是通過無數的媒體查詢來給頁面返回一套適用的CSS樣式程式碼,這套CSS程式碼中會有很多的背景圖片,背景圖片的地址是一個特定的URL,這個URL上攜帶了一些我們需要收集的引數,比如:

@media screen and (width: 300px) {
body {
background: url(https://zero2one/collect/info/width=300);
}
}

這個媒體查詢程式碼只會在使用者裝置寬度為 300px 時生效,所以我們上報的地址也可以帶上 width=300 的資訊,其它資訊也類似這種方案去實現,這裡就不一一列舉了

為了避免資訊的重複上報,服務端在接收到該資訊上報後,最好將 HTTP 的狀態碼返回 410(Gone) ,這樣該請求就會快取下來,之後重複的請求都不會走到服務端,而是走的快取。最終的效果就類似這樣:

Status 410(Gone)

同樣的,使用者本地安裝了哪些字型也可以追蹤到,不過實現起來有些麻煩,我們可以列舉幾百甚至幾千種字型樣式程式碼,讓頁面去本地載入對應字型,若本地沒有該字型則發起網路請求到我們的服務端,最後對比一下哪幾個字型沒上報,就說明使用者本地有哪些字型了~

舉個例子:chestnut::

@font-face{
font-family: abeezee;
font-display: block;
src: local(Abeezee), /* 載入本地字型 Abeezee */
url(/collect/info/font-name=Abeezee) /* 若載入失敗,則上報資訊 font-name=Abeezee */
}
@font-face{
font-family: abel;
font-display: block;
src: local(Abel), /* 載入本地字型 Abel */
url(/some/url/font-name=Abel) /* 若載入失敗,則上報資訊 font-name=Abel */
}
/* ...此處省略成百上千個類似字型樣式程式碼 */

若最後我們服務端所有字型的資訊上報都收到了,唯獨沒有收到 /collect/info/font-name=Abeezee 這條請求,說明該使用者本地只安裝了 Abeezee 這個字型

再多舉幾個例子:chestnut:,判斷使用者當前是哪個瀏覽器:

@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){
body {
background: url(https://zero2one/collect/info/browser=IE10+)
}
}

@media all and (min-width:0) {
body {
background: url(https://zero2one/collect/info/browser=IE9+)
}
}

@-moz-document url-prefix() {
body {
background: url(https://zero2one/collect/info/browser=firefox)
}
}

@media screen and (-webkit-min-device-pixel-ratio:0) {
body {
background: url(https://zero2one/collect/info/browser=chrome)
}
}

更多的使用者資訊可以見媒體查詢支援的功能

媒體功能

為何要有CSS指紋

因為大多數的使用者隱私追蹤都是依賴於 JavaScriptCookies 的,尤其是 Cookies ,你在大多數的網站都能見到一個彈窗向你請求 Cookies的訪問許可權,例如 stack overflow 網站一進去就會出彈框:

但無論是 JavaScript 還是 Cookies ,都很容易反追蹤,比如 禁用頁面的 JS指令碼連線VPN 或者是 使用了某些反追蹤的瀏覽器外掛 等等,而 CSS指紋 就可以不受這些的限制而收集到使用者相關資料

CSS指紋缺點

上述所說的方案有一個最大的缺點就是:CSS檔案會特別大,如果帶上所有字型的請求,甚至瀏覽器併發的請求都會達到數百個,這一定是會影響使用者體驗的

優化請求次數

要知道使用者本地有哪些字型的代價比較高,或許可以通過本地有哪些字型來判斷使用者當前的作業系統,因為市面上大部分的作業系統自帶的那些預設本地字型就只有那些,直接去請求這些字型就足夠了~

我們現在每收集一條資訊都要上報一次,如果不算字型請求上報,估計併發的上報請求也有幾十條了,有一種思路就是 請求合併 ,看看能否把所有要收集的引數拼接到一個URL上去,但目前為止僅在CSS裡好像是不行的,因為 url() 是不能使用自定義CSS變數的,來看個例子:

::root {
--request-url: 'https://zero2one/collect/info/width=300'
}


body {
background: url(var(--request-url)); /* error */
}

這樣用是不行的,為什麼?大家都知道 url() 裡的內容既可以加引號,也可以不加:

body {
background: url(https://zero2one/collect/info/width=300); /* right */
}

.root {
background: url("https://zero2one/collect/info/width=300"); /* right */
}

這是歷史遺留問題導致的,所以如果我們想在 url() 裡使用自定義變數就會報錯,CSS在解析時會把 url() 中的所有內容當做 URL,而現在值中有非轉義 ( 會導致一個分析的錯誤,所以整個宣告被作為無效丟擲,那麼想用自定義變數該怎麼使用呢?

::root {
--request-url: url('https://zero2one/collect/info/width=300')
}


body {
background: var(--request-url); /* right */
}

這樣就沒有問題了!

好了言歸正傳,正是因為這樣的問題,我們似乎沒法對使用者資料做一個拼接合並上報,所以 CSS Values and Units Module Level 4 (你們也可以叫它CSS4)提出了這樣一個草案

url草案

如果看不懂這個語法解釋的同學可以去看我之前寫的一篇CSS語法自學指南: 熱議:CSS為什麼這麼難學?一定是你的方法不對

簡單來說的話,就是 url() 裡面可以填入 字串 + 0或多個修飾符 ,去看了一下修飾符的含義,似乎修飾符可以寫CSS函式(類似 calc()var()attr() ...),這樣不就滿足我們的需求了嗎?來模擬寫一下:

::root {
--screen-width: 'width=300';
--screen-height: '&height=500';
}

body {
background: url('https://zero2one/collect/info?' var(--screen-width) var(--screen-height));
}

這樣就模擬了我們 JS 中的字串拼接~ 豈不是美哉,然後再加上其它媒體查詢的變數修改,就實現了一次請求上報所有資訊的需求

::root {
--screen-width: '';
--screen-height: '';
}

@media screen and (width: 300px) {
::root {
--screen-width: 'width=300';
}
}

@media screen and (height: 500px) {
::root {
--screen-height: '&height=500';
}
}

body {
background: url('https://zero2one/collect/info?' var(--screen-width) var(--screen-height));
}

希望這個草案能順利通過~ 這樣CSS指紋的方案就又更加完善了!

END

本文對於 CSS指紋 的探討就到這裡了,如果有什麼問題或者想法,可以在評論區留言,我們互相交流討論!

我是 零一 ,分享技術,不止前端!我們下期見~

最後,歡迎加我的微信,拉你進 上百人的前端交流群

分享

收藏

點贊

在看