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