分享一篇老文章。前些年在 ASRC 上的看了一篇文章「Html5新功能上的ui-redressing」后,寫的一些改進方案。
文件對話框
文件上傳對話框是一直以來就存在的網頁控件。
到了 HTML5 時代,增加了更多的功能,例如支持文件多選。Chrome 甚至還支持「上傳文件夾」這一私有特征:
<input type="file" webkitdirectory />
在給用戶方便的同時,其安全隱患也逐漸出現。用戶平時在下載時,理所當然的彈出的是保存對話框,因此常常不仔細看就做出了選擇。
這極有可能被攻擊者所利用。一些惡意網站在用戶點擊下載時,故意彈出一個上傳對話框。只要用戶一疏忽,就把選中的文件夾給上傳了!
下載對話框
上傳對話框
當然,僅僅依靠默認的上傳,這種攻擊方式仍有較大難度。因為整個文件夾可能非常大,上傳需要很久的時間。
如果用戶等了半天也沒看見下載進度,或許就會刷新重試,甚至放棄了。
選擇即授權
然而,HTML5 帶來了一個新的規范 —— File API,允許腳本訪問文件。
但由於沙箱限制,腳本無法訪問任何一個本地文件,除非用戶主動授權。如何授權?最常見的,就是「上傳對話框」了。
事實上,如今的上傳對話框,早已不是從前「選擇哪個文件」的功能,而是「允許腳本訪問哪個文件」的權限申請!只不過界面上沒有提示罷了。
例如,一個傳統的文件上傳控件。當用戶選中文件后,即可通過 File API 讀取文件內容:
<input id="dialog" type="file" />
<script>
dialog.onchange = function(e) {
var reader = new FileReader();
reader.onload = function() {
console.log(this.result);
};
reader.readAsText(this.files[0]);
};
</script>
或許你已注意到,File 位於files[]
而不是file
,這正是給文件夾預留的!
在 Chrome 里,上傳控件只要加上 webkitdirectory
屬性,就變成文件夾選擇框。這時一旦用戶選中某個文件夾,瞬間就賜予腳本訪問整個文件夾的權限!
<input id="dialog" type="file" webkitdirectory />
<script>
dialog.onchange = function(e) {
var files = this.files;
var table = {};
for (var i = 0; i < files.length; i++) {
var f = files[i];
var dt = new Date(f.lastModified);
table[i] = {
path: f.webkitRelativePath,
size: f.size,
modified: dt.toLocaleString()
};
}
console.table(table);
};
</script>
於是,用戶本想將文件保存在桌面上,結果卻將桌面上的所有資料,被攻擊者的腳本拿到!
優化上傳
一旦腳本可主動訪問,我們可以用更靈活的方式處理這些文件,無需再用傳統落后的方式上傳。我們可以直接在前端分析出「有價值」的文件,例如:
-
備注文件、腳本、批處理、電子表格等,很可能存有一些敏感信息,而且體積小價值大,優先將其上傳;
-
圖片則可通過 canvas 縮放,先傳較小的縮略圖。當接收端發現有意義時,再傳輸原文件。
-
對於一些體積較大但意義不大的文件,則可以直接忽略。
由於 HTTP 上傳是沒有壓縮的,因此在傳輸文本文件時效率很低。我們可以借助 Flash 內置的LZMA
壓縮算法,極大提升傳輸效率。如果不支持 Flash,也可以使用asm.js
版的 LZMA 壓縮器,配合Worker
線程在后台壓縮和傳輸。
同時,將多個小文件合並后再壓縮,可進一步提高壓縮率。再多開幾個連接,上傳速度即可大幅提升。
續點上傳
不過即使再優化,仍有傳不完的可能。因此,我們得將沒傳完的內容儲存起來,當用戶再次回來時,繼續傳輸。
得益於 HTML5 的 Storage API,這不難實現。
事實上,當用戶授權了某個文件夾時,我們首先要做的不是發送,而是讀出文件夾內容,立即備份到 Storage 里。畢竟,文件的讀取需要用戶主動配合,機會是非常珍貴的;而 Storage 的訪問則無需任何條件。
當備份完成后,再從 Storage 里一塊一塊的讀取、發送、刪除。這樣,即使中途頁面刷新或關閉了,下次回來時,仍能從 Storage 中繼續。
考慮到每個域的 Storage 容量有限,我們可以使用iframe
嵌入多個不同域的頁面,然后通過postMessage
進行數據的分發和匯總,這樣就不受容量限制了。
當然,能不能無限容量還得看瀏覽器策略,不然硬盤會被撐滿
將數據存放在 Storage 里還有另一個好處,即使用戶永不回來,但數據仍持久保存着。只要以后一旦進入其他的站點,只要是我們可控的,仍有機會繼續上傳。(例如將用戶引到我們布置了 XSS 的站點上)
延長上傳
在之前《延長 XSS 生命期》 中介紹過,可以使用各種黑魔法來提升腳本有效期。
利用這個原理,即使當前頁面關閉,其他關聯的頁面也能繼續上傳。事實上,除了文中提到的方法,如今還有一個新的 API —— SharedWorker,它可以讓 Worker 共享於多個頁面,只要有一個存在,線程就不會停止。可以讓續點時丟失的數據更少。
視覺欺騙
由於上傳控件有着獨特的界面,怎樣才能讓用戶不小心點到呢?萬能的方法是點擊劫持(Clickjacking)。
不過本場景無需這么麻煩,只需簡單的調用控件的click
方法就可以了。
<a href="ed2k://|file|xxx.avi" id="download">高速下載</a>
<script>
var uploader = document.createElement('input');
uploader.type = 'file';
uploader.webkitdirectory = true;
download.onclick = function(e) {
uploader.click(); // 彈出上傳對話框
e.preventDefault(); // 屏蔽下載對話框
};
</script>
我們屏蔽超鏈接的默認行為,用上傳控件的點擊事件取而代之,即可召喚出「文件夾授權」對話框了!
同時,為了不讓眼亮的人發現對話框上的破綻,我們使用一些第三方的下載方式,例如電驢、迅雷等等,讓人們誤以為就是這樣的。
當然,這只是一個小例子。只要頁面做的真實,下載內容引人入勝,用戶自然就會中招。
后記
本以為 webkitdirectory
的這個私有屬性很快就會放棄,至少是更強的安全提示。不過至少現在也沒更新,因此上網時還是要多加留心,看清楚了再做決定。
當然,這篇只是最早的「交互欺騙」探索。事實上,深入挖掘會發現可利用點遠不僅此。
如今的瀏覽器在視覺、音頻上的體驗已經非常完善,攻擊者甚至可以在網頁里,高度模擬一個本地應用的交互效果。讓用戶誤以為是瀏覽器之外的程序彈出的界面,從而進行釣魚。