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




















