XSS定義
XSS, 即為(Cross Site Scripting), 中文名為跨站腳本, 是發生在目標用戶的瀏覽器層面上的,當渲染DOM樹的過程成發生了不在預期內執行的JS代碼時,就發生了XSS攻擊。
跨站腳本的重點不在‘跨站’上,而在於‘腳本’上。大多數XSS攻擊的主要方式是嵌入一段遠程或者第三方域上的JS代碼。實際上是在目標網站的作用域下執行了這段js代碼。
XSS攻擊方式
反射型 XSS
反射型XSS,也叫非持久型XSS,是指發生請求時,XSS代碼出現在請求URL中,作為參數提交到服務器,服務器解析並響應。響應結果中包含XSS代碼,最后瀏覽器解析並執行。
從概念上可以看出,反射型XSS代碼是首先出現在URL中的,然后需要服務端解析,最后需要瀏覽器解析之后XSS代碼才能夠攻擊。
舉一個小栗子。
使用express起一個web服務器,然后設置一下請求接口。通過ajax的GET請求將參數發往服務器,服務器解析成json后響應。將返回的數據解析后顯示到頁面上。(沒有對返回的數據進行解碼和過濾等操作。)
html <textarea name="txt" id="txt" cols="80" rows="10"> <button type="button" id="test">測試</button> js var test = document.querySelector('#test') test.addEventListener('click', function () { var url = `/test?test=${txt.value}` // 1. 發送一個GET請求 var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { // 3. 客戶端解析JSON,並執行 var str = JSON.parse(xhr.responseText).test var node = `${str}` document.body.insertAdjacentHTML('beforeend', node) } else { console.log('error', xhr.responseText) } } } xhr.open('GET', url, true) xhr.send(null) }, false) express var express = require('express'); var router = express.Router(); router.get('/test', function (req, res, next) { // 2. 服務端解析成JSON后響應 res.json({ test: req.query.test }) })
現在我們通過給textarea添加一段有攻擊目的的img標簽,
<img src="null" onerror='alert(document.cookie)' />
實際的頁面時這樣的。
ok現在,我們點擊<測試>按鈕,一個XSS攻擊就發生了。下面圖片中是獲取了本地的部分cookie信息
實際上,我們只是模擬攻擊,通過alert獲取到了個人的cookie信息。但是如果是黑客的話,他們會注入一段第三方的js代碼,然后將獲取到的cookie信息存到他們的服務器上。這樣的話黑客們就有機會拿到我們的身份認證做一些違法的事情了。
以上,存在的一些問題,主要在於沒有對用戶輸入的信息進行過濾,同時沒有剔除掉DOM節點中存在的一些有危害的事件和一些有危害的DOM節點。
存儲型 XSS
存儲型XSS,也叫持久型XSS,主要是將XSS代碼發送到服務器(不管是數據庫、內存還是文件系統等。),然后在下次請求頁面的時候就不用帶上XSS代碼了。
最典型的就是留言板XSS。用戶提交了一條包含XSS代碼的留言到數據庫。當目標用戶查詢留言時,那些留言的內容會從服務器解析之后加載出來。瀏覽器發現有XSS代碼,就當做正常的HTML和JS解析執行。XSS攻擊就發生了。
DOM XSS
DOM XSS攻擊不同於反射型XSS和存儲型XSS,DOM XSS代碼不需要服務器端的解析響應的直接參與,而是通過瀏覽器端的DOM解析。這完全是客戶端的事情。
DOM XSS代碼的攻擊發生的可能在於我們編寫JS代碼造成的。我們知道eval語句有一個作用是將一段字符串轉換為真正的JS語句,因此在JS中使用eval是很危險的事情,容易造成XSS攻擊。避免使用eval語句。
如以下代碼
test.addEventListener('click', function () { var node = window.eval(txt.value) window.alert(node) }, false) txt中的代碼如下 <img src='null' onerror='alert(123)' />
以上通過eval語句就造成了XSS攻擊。
XSS危害
- 通過document.cookie盜取cookie
- 使用js或css破壞頁面正常的結構與樣式
- 流量劫持(通過訪問某段具有window.location.href定位到其他頁面)
- Dos攻擊:利用合理的客戶端請求來占用過多的服務器資源,從而使合法用戶無法得到服務器響應。
- 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻擊)用戶的身份執行一些管理動作,或執行一些一般的如發微博、加好友、發私信等操作。
- 利用可被攻擊的域受到其他域信任的特點,以受信任來源的身份請求一些平時不允許的操作,如進行不當的投票活動。
XSS防御
從以上的反射型和DOM XSS攻擊可以看出,我們不能原樣的將用戶輸入的數據直接存到服務器,需要對數據進行一些處理。以上的代碼出現的一些問題如下
- 沒有過濾危險的DOM節點。如具有執行腳本能力的script, 具有顯示廣告和色情圖片的img, 具有改變樣式的link, style, 具有內嵌頁面的iframe, frame等元素節點。
- 沒有過濾危險的屬性節點。如事件, style, src, href等
- 沒有對cookie設置httpOnly。
如果將以上三點都在渲染過程中過濾,那么出現的XSS攻擊的概率也就小很多。
對cookie的保護
- 對重要的cookie設置httpOnly, 防止客戶端通過
document.cookie
讀取cookie。服務端可以設置此字段。
對用戶輸入數據的處理
- 編碼:不能對用戶輸入的內容都保持原樣,對用戶輸入的數據進行字符實體編碼。對於字符實體的概念可以參考文章底部給出的參考鏈接。
- 解碼:原樣顯示內容的時候必須解碼,不然顯示不到內容了。
- 過濾:把輸入的一些不合法的東西都過濾掉,從而保證安全性。如移除用戶上傳的DOM屬性,如onerror,移除用戶上傳的Style節點,iframe, script節點等。
通過一個例子講解一下如何處理用戶輸入的數據。
實現原理如下:
- 存在一個parse函數,對輸入的數據進行處理,返回處理之后的數據
- 對輸入的數據(如DOM節點)進行解碼(使用第三方庫 he.js)
- 過濾掉一些元素有危害的元素節點與屬性節點。如script標簽,onerror事件等。(使用第三方庫HTMLParser.js)
<script src='/javascripts/htmlparse.js'></script> <script src='/javascripts/he.js'></script> // 第三方庫資源在文章底部給出 // parse函數實現如下 function parse (str) { // str假如為某個DOM字符串 // 1. result為處理之后的DOM節點 let result = '' // 2. 解碼 let decode = he.unescape(str, { strict: true }) HTMLParser(decode, { start (tag, attrs, unary) { // 3. 過濾常見危險的標簽 if (tag === 'script' || tag === 'img' || tag === 'link' || tag === 'style' || tag === 'iframe' || tag === 'frame') return result += `<${tag}` for (let i = 0; i < attrs.length; i++) { let name = (attrs[i].name).toLowerCase() let value = attrs[i].escaped // 3. 過濾掉危險的style屬性和js事件 if (name === 'style' || name === 'href' || name === 'src' || ~name.indexOf('on')) continue result += ` ${name}=${value}` } result += `${unary ? ' /' : ''} >` }, chars (text) { result += text }, comment (text) { result += `<!-- ${text} -->` }, end (tag) { result += `</${tag}>` } }) return result } 因此,有了以上的parse函數之后,就可以避免大部分的xss攻擊了。 test.addEventListener('click', function () { // ... 省略部分代碼 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { // 3. 客戶端解析JSON,並執行 // test按鈕的點擊事件中唯一的變化就是使用parse對服務端返回的數據進行了解碼和過濾的處理。 var str = parse(JSON.parse(xhr.responseText).test) // 通過parse解析之后返回的數據就是安全的DOM字符串 var node = `${str}` document.body.insertAdjacentHTML('beforeend', node) } } } // ... 省略部分代碼 }, false)
那么,栗子說完了。
稍微總結一下
- 一旦在DOM解析過程成出現不在預期內的改變(JS代碼執行或樣式大量變化時),就可能發生XSS攻擊
- XSS分為反射型XSS,存儲型XSS和DOM XSS
- 反射型XSS是在將XSS代碼放在URL中,將參數提交到服務器。服務器解析后響應,在響應結果中存在XSS代碼,最終通過瀏覽器解析執行。
- 存儲型XSS是將XSS代碼存儲到服務端(數據庫、內存、文件系統等),在下次請求同一個頁面時就不需要帶上XSS代碼了,而是從服務器讀取。
- DOM XSS的發生主要是在JS中使用eval造成的,所以應當避免使用eval語句。
- XSS危害有盜取用戶cookie,通過JS或CSS改變樣式,DDos造成正常用戶無法得到服務器響應。
- XSS代碼的預防主要通過對數據解碼,再過濾掉危險標簽、屬性和事件等。
參考資源
- 《WEB前端黑客技術揭秘》
- 淺談XSS攻擊的那些事(附常用繞過姿勢)
- XSS實戰:我是如何拿下你的百度賬號
- HTMLParser
- he
- Web安全-XSS