別天真了,第三方CSS并不安全
最近一段時間,關(guān)于 通過 CSS 創(chuàng)建 “keylogger”(鍵盤記錄器) 的討論很多。
有些人呼吁瀏覽器廠商去“修復(fù)”它。有些人則深入研究,表示它僅能影響通過類 React 框架建立的網(wǎng)站,并指責 React。而真正的問題卻在于很多人認為第三方內(nèi)容是“安全”的。
第三方圖片
- <img src="https://example.com/kitten.jpg">
如果我將上述代碼引入我的文件中,即表示信任 example.com。對方可能會刪除資源,給我一個 404,導(dǎo)致網(wǎng)站不完整,從而破壞這種信任關(guān)系。或者,他們可能會用其他非預(yù)期的數(shù)據(jù)來替代小貓圖片的數(shù)據(jù)。
但是,圖片的影響僅限于元素本身的內(nèi)容區(qū)域。我可以向用戶解釋并希望用戶相信,“此處的內(nèi)容來自 example.com,如果它有誤,則是原站點的問題,并不是本站造成的”。但這個問題肯定不會影響到密碼輸入框等內(nèi)容。
第三方腳本
- <script src="https://example.com/script.js"></script>
與圖片相比,第三方腳本則有更多的控制權(quán)。如果我將上述代碼引入我的文件中,則表示我賦予了 example.com 完全控制我的網(wǎng)站的權(quán)限。該腳本能:
- 讀取/修改頁面內(nèi)容。
- 監(jiān)聽用戶的所有交互。
- 運行耗費大量計算資源的代碼(如 cryptocoin 挖礦程序)。
- 通過向本站發(fā)請求,這樣能附帶用戶的 cookie,轉(zhuǎn)發(fā)響應(yīng)。(譯注:盜取用戶的 cookie 及其他數(shù)據(jù))
- 讀取/修改本地存儲。
- ......可以做任何對方想做的事情。
“本地存儲”非常重要。如果腳本通過 IndexedDB 或緩存 API 發(fā)起攻擊,則即使在刪除腳本后,攻擊仍可能在整個站點內(nèi)繼續(xù)存在。
如果你引入了其他站點的腳本,則必須絕對相信對方及對方的防護能力。
如果你遭到惡意腳本的攻擊,則可設(shè)置 Clear-Site-Data header(清空站點數(shù)據(jù)響應(yīng)頭) 清除站點所有數(shù)據(jù)。
第三方CSS
- <link rel="stylesheet" href="https://example.com/style.css">
相比圖片,CSS 在能力上更接近腳本。像腳本一樣,它適用于整個頁面。它可以:
- 刪除/添加/修改頁面內(nèi)容。
- 根據(jù)頁面內(nèi)容發(fā)起請求。
- 可響應(yīng)多種用戶交互。
雖然 CSS 不能修改本地存儲,也不能通過 CSS 運行 cryptocoin 挖礦程序(也許是可能的,只是我不知道而已),但惡意 CSS 代碼仍然能造成很大的損失。
鍵盤記錄器
從引起廣泛關(guān)注的代碼開始講起:
- input[type="password"][value$="p"] {
- background: url('/password?p');
- }
如果輸入框的 value 屬性值以 p 結(jié)尾,上述代碼將會向 /password?p 發(fā)起請求。每個字符都可觸發(fā)這個操作,通過它能獲取到很多數(shù)據(jù)。
默認情況下,瀏覽器不會將用戶輸入的值存儲在 value 屬性中,因此這種攻擊需要依賴某些能同步這些值的東西,如 React。
要應(yīng)對這個問題,React 可用另一種同步密碼字段的方式,或瀏覽器可限制那些能匹配密碼字段屬性的選擇器。但是,這僅僅是一種虛假的安全。你只解決了在特殊情況下的該問題,而其他情況依舊。
如果 React 改為使用 data-value 屬性,則該應(yīng)對方法無效。如果網(wǎng)站將輸入框更改為 type="text",以便用戶可以看到他們正在輸入的內(nèi)容,則該應(yīng)對方法無效。如果網(wǎng)站創(chuàng)建了一個 <better-password-input> 組件并暴露 value 作為屬性,則該應(yīng)對方法無效。
此外,還有很多其他的基于 CSS 的攻擊方式:
消失的內(nèi)容
- body {
- display: none;
- }
- html::after {
- content: 'HTTP 500 Server Error';
- }
以上是一個極端的例子,但想象一下,如果第三方僅對某一小部分用戶這樣做。不但你很難調(diào)試,還會失去用戶的信任。
更狡猾的方式如偶爾刪除“購買”按鈕,或重排內(nèi)容段落。
添加內(nèi)容
- .price-value::before {
- content: '1';
- }
哦,價格被標高了。
移動內(nèi)容
- .delete-everything-button {
- opacity: 0;
- position: absolute;
- top: 500px;
- left: 300px;
- }
上面的按鈕能做一些重要的操作,設(shè)置其為不可見,然后放在用戶可能點擊的地方。
值得慶幸的是,如果按鈕的操作確實非常重要,網(wǎng)站可能會先顯示確認對話框。但也不是不可繞過,只需使用更多的 CSS 來欺騙用戶點擊 “確定” 按鈕而不是“取消”按鈕即可。
假設(shè)瀏覽器確實采用上面的應(yīng)對方法解決“鍵盤記錄器”的問題。攻擊者只需在頁面上找到一個非密碼文本輸入框(可能是搜索輸入框)并將其蓋在密碼輸入框上即可。然后他們的攻擊就又可用了。
讀取屬性
其實,你需要擔心的不僅僅是密碼輸入框。你可能在屬性中保存著其他的隱藏內(nèi)容:
- <input type="hidden" name="csrf" value="1687594325">
- <img src="/avatars/samanthasmith83.jpg">
- <iframe src="//cool-maps-service/show?st-pancras-london"></iframe>
- <img src="/gender-icons/female.png">
- <div></div>
所有這些都可以通過 CSS 選擇器獲取,且能發(fā)出請求。
監(jiān)聽交互
- .login-button:hover {
- background: url('/login-button-hover');
- }
- .login-button:active {
- background: url('/login-button-active');
- }
可將 hover 和 active 狀態(tài)發(fā)送到服務(wù)器。通過適當?shù)?CSS,你就能獲取到用戶意圖。
讀取文本
- @font-face {
- font-family: blah;
- src: url('/page-contains-q') format('woff');
- unicode-range: U+85;
- }
- html {
- font-family: blah, sans-serif;
- }
在這種情況下,如果頁面內(nèi)有 q 字符,則會發(fā)送請求。你可以為不同的字符,并針對特定的元素,創(chuàng)建大量不同的字體。字體也可以包含 ligature(連字),所以你可以在開始檢測字符序列。你甚至可以通過將字體技巧與滾動條檢測結(jié)合起來 來推斷內(nèi)容。
譯注:關(guān)于 ligature(連字), 可查看 Wikipedia Typographic Ligature
第三方內(nèi)容不安全
這些只是我所知道的一些技巧,我相信還有更多。
第三方內(nèi)容在其沙箱區(qū)域內(nèi)具有強大的能力。一張圖片或沙盒化的 iframe 僅在一個小范圍內(nèi)的沙箱中,但腳本和樣式的范圍卻是你的頁面,甚至是整個站點。
如果你擔心惡意用戶誘使你的網(wǎng)站加載第三方資源,可以通過 CSP 用作防護手段,其可以限制加載圖片,腳本和樣式的來源。
你還可以使用 Subresource Integrity(子資源完整性 ) 來確保腳本/樣式的內(nèi)容匹配特定的 hash,否則將不加載。感謝 Hacker News上的Piskvorrr 提醒我!
如果你想了解更多如上述的 hack 技巧,包括滾動條技巧,更多信息請參閱 Mathias Bynens' talk from 2014,Mike West's talk from 2013,或 Mario Heiderich et al.'s paper from 2012(PDF)。是的,這不是什么新東西。