前言
XSS 攻擊:即跨站腳本攻擊,它是 Web 程式中常見的漏洞。原理是攻擊者往 Web 頁面裡插入惡意的腳本代碼(css 代碼、Javascript 代碼等),當使用者浏覽該頁面時,嵌入其中的腳本代碼會被執行,進而達到惡意攻擊使用者的目的,如盜取使用者 cookie、破壞頁面結構、重定向到其他網站等。
預防:
1、擷取使用者的輸入,不用innerHtml,用innerText.
2、預防 XSS 的核心是必須對輸入的資料做過濾處理。對使用者的輸入進行過濾,如對& < > " ’ /等進行轉義。
XSS案例
說明:這裡用一個XSS案例更好的說明XSS攻擊,重點感謝美團技術團隊的案例支援。
某天,公司需要一個搜尋頁面,根據 URL 參數決定關鍵詞的内容。小明很快把頁面寫好并且上線。代碼如下:
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:<%= getParameter("keyword") %>
</div>
然而,在上線後不久,小明就接到了安全組發來的一個神秘連結:
http://xxx/search?keyword="><script>alert('XSS');</script>
小明帶着一種不祥的預感點開了這個連結[請勿模仿,确認安全的連結才能點開]。果然,頁面中彈出了寫着"XSS"的對話框。
可惡,中招了!小明眉頭一皺,發現了其中的奧秘:
當浏覽器請求 http://xxx/search?keyword=">,拼接到 HTML 中傳回給浏覽器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:"><script>alert('XSS');</script>
</div>
浏覽器無法分辨出 是惡意代碼,因而将其執行。
這裡不僅僅 div 的内容被注入了,而且 input 的 value 屬性也被注入, alert 會彈出兩次。
面對這種情況,我們應該如何進行防範呢?
其實,這隻是浏覽器把使用者的輸入當成了腳本進行了執行。那麼隻要告訴浏覽器這段内容是文本就可以了。
聰明的小明很快找到解決方法,把這個漏洞修複
<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:<%= escapeHTML(getParameter("keyword")) %>
</div>
經過了轉義函數的處理後,最終浏覽器接收到的響應為:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜尋</button>
<div>
您搜尋的關鍵詞是:"><script>alert('XSS');</script>
</div>
通過這個事件,學習到了如下知識:
1.通常頁面中包含的使用者輸入内容都在固定的容器或者屬性内,以文本的形式展示。
2.攻擊者利用這些頁面的使用者輸入片段,拼接特殊格式的字元串,突破原有位置的限制,形成了代碼片段。
3.攻擊者通過在目标網站上注入腳本,使之在使用者的浏覽器上運作,進而引發潛在風險。
4. 通過 HTML 轉義,可以防止 XSS 攻擊。<span style="color:red">[事情當然沒有這麼簡單啦!請繼續往下看]</span>。
注意特殊的 HTML 屬性、JavaScript API
自從上次事件之後,小明會小心的把插入到頁面中的資料進行轉義。而且他還發現了大部分模闆都帶有的轉義配置,讓所有插入到頁面中的資料都預設進行轉義。這樣就不怕不小心漏掉未轉義的變量啦,于是小明的工作又漸漸變得輕松起來。
但是,作為導演的我,不可能讓小明這麼簡單、開心地改 Bug
不久,小明又收到安全組的神秘連結:
http://xxx/?redirect_to=javascript:alert(‘XSS’)。小明不敢大意,趕忙點開頁面。然而,頁面并沒有自動彈出萬惡的“XSS”。
小明打開對應頁面的源碼,發現有以下内容:
<a href="<%= escapeHTML(getParameter(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" redirect_to")) %>">跳轉...</a>
這段代碼,當攻擊 URL 為 http://xxx/?redirect_to=javascript:alert(‘XSS’),服務端響應就成了:
<a href="javascript:alert('XSS')" target="_blank" rel="external nofollow" >跳轉...</a>
雖然代碼不會立即執行,但一旦使用者點選 a 标簽時,浏覽器會就會彈出“XSS”。
在這裡,使用者的資料并沒有在位置上突破我們的限制,仍然是正确的 href 屬性。但其内容并不是我們所預期的類型。
原來不僅僅是特殊字元,連 javascript: 這樣的字元串如果出現在特定的位置也會引發 XSS 攻擊。
小明眉頭一皺,想到了解決辦法:
// 禁止 URL 以 "javascript:" 開頭
xss = getParameter("redirect_to").startsWith('javascript:');
if (!xss) {
<a href="<%= escapeHTML(getParameter(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" redirect_to"))%>">
跳轉...
</a>
} else {
<a href="/404" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
跳轉...
</a>
}
隻要 URL 的開頭不是 javascript:,就安全了吧?
安全組随手又扔了一個連接配接:http://xxx/?redirect_to=jAvascRipt:alert(‘XSS’)
,在判斷 URL 開頭是否為 javascript: 時,先把使用者輸入轉成了小寫,然後再進行比對。
不過,所謂“道高一尺,魔高一丈”。面對小明的防護政策,安全組就構造了這樣一個連接配接:
http://xxx/?redirect_to=%20javascript:alert('XSS')
%20javascript:alert('XSS')
經過 URL 解析後變成 javascript:alert(‘XSS’),這個字元串以空格開頭。這樣攻擊者可以繞過後端的關鍵詞規則,又成功的完成了注入。
最終,小明選擇了白名單的方法,徹底解決了這個漏洞:
// 根據項目情況進行過濾,禁止掉 "javascript:" 連結、非法 scheme 等
allowSchemes = ["http", "https"];
valid = isValid(getParameter("redirect_to"), allowSchemes);
if (valid) {
<a href="<%= escapeHTML(getParameter(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" redirect_to"))%>">
跳轉...
</a>
} else {
<a href="/404" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >
跳轉...
</a>
}
通過這個事件,小明學習到了如下知識:
1、做了 HTML 轉義,并不等于高枕無憂。
2、對于連結跳轉,如 <a href=“xxx” 或 location.href=“xxx”,要檢驗其内容,禁止以 javascript: 開頭的連結,和其他非法的 scheme。
作者:狂歡