通過img URL實施XSS的解決方案
今天在整理一些js,9月份的時候給百姓網支持了markdown,但 UGC 的自由意味著我們要做一些保持措施,比如防XSS攻擊。翻到了9月初寫的一篇郵件。當時的背景是,當天上了markdown支持,晚上覺得似乎不太對,結果 23 點后開始研究 XSS 攻擊。其中有一個難點,如何防止通過外部圖片鏈接進行 XSS。我的解決方案在郵件原文做了簡單的介紹:
通過XSS的方法好多啊,特別是image的方式,而我們今天發布的viewad markdown支持是可以使用img.src進行外鏈的,因此需要考慮這樣的問題,今晚11點多回來研究了一下,主要可以歸類為以下幾種:
src="javascript:alert(1)"
src="jav ascript:alert(2)"
src="java�script:alert(3)"
src="� ……."
src="上面4中的變種"
src="外部執行腳本鏈接" www.hack6.com
Google 了很久,沒有現成所工具,而且對于第6種的解法都很浪費資源,只能自己通過 http://ha.ckers.org/xss.html 上的總結,分析得出了上面幾種類型,在 Markdown parser 中進行 xss 類型檢測支持。還真是多虧了有 ha.ckers 的總結,這個函數可以寫得非常簡單:
function imageXSS($img){
return preg_match('/(?:javascript|jav\s+ascript|\&#\d+|\&#x)/i', $img);
}
而解除第6種XSS方法,判斷外部資源是最麻煩的。
Google 得來的結論:總的來說是通過get_headers 和 stream_get_meta_data等取到content-Type的方式來做,需要我們的服務器進行請求,并且需要分析來源,再根據content-Type決定,這樣做會有一些問題:后端渲染的數據要等 header 取過來,即加載圖片后(可能有多個并且可能非常大),會阻礙起來導致渲染非常慢不緩存且每個圖片都請求,浪費服務器資源。即使是一個flickr上500px的圖,在我20M的網絡下加載也要49ms。可能一不小心就會導致服務器掛了(如果圖片多和訪問的人多的話,這個是不緩存圖片 url 的)。
搞了很久,最終想到preload image的方式,根據測試看來,在瀏覽器中只有加載的內容是真正的image才會觸發圖片的onload事件,那么其實我們可以利用onload來解決這樣的問題。這樣我們可以把請求分布給每一個用戶,而不需要我們任何一點資源,并且這種方式還會進行并行加載,甚至更提升了viewad的速度。大概需要做下面幾步:
默認情況下不加載img的src,而是設置data-xssimg="圖片地址",檢測圖片的onload事件,如果沒有觸發onload則不顯示,不過當src為空的時候,可能在一些瀏覽器會影響網站的渲染速度,所以在error觸發的時候引用了一個永久緩存的圖片:http://static.baixing.net/images/nopic_small.png。
而這些代碼看起來如下(已經變成一個 jQuery 組件),目前這段代碼已經放在 github 上,你可以在這里查看:imagesXSS.js:
~function ($) {
$.fn.imageXSS = function () {
this.each(function () {
var that = $(this),
url = that.data('mdimg'),
img = document.createElement('img');
$(img).on('load', function () {
that.attr('src', url);
})
$(img).on('error', function () {
that.attr('src', 'http://static.baixing.net/images/nopic_small.png');
})
img.src = url;
})
}
$('[data-mdimg]').imageXSS();
}(jQuery);
目前也只能想到這個點上。基本上測試過的都沒有問題。線上目前已經支持這個版本。周一回去再提交一個版本。