如何保護(hù)網(wǎng)頁(yè)按鈕不被XSS自動(dòng)點(diǎn)擊
前言
XSS 自動(dòng)點(diǎn)按鈕有什么危害?
在社交網(wǎng)絡(luò)里,很多操作都是通過(guò)點(diǎn)擊按鈕發(fā)起的,例如發(fā)表留言。假如留言系統(tǒng)有 XSS 漏洞,用戶中招后 XSS 除了攻擊之外,還能進(jìn)行傳播 —— 它能自動(dòng)填入留言內(nèi)容,并點(diǎn)擊發(fā)表按鈕,即可發(fā)出帶有惡意代碼的留言。好友看了中招后,又傳播給他們的好友。。。從而形成蠕蟲(chóng)擴(kuò)散。
那么,有沒(méi)有一種機(jī)制,讓「發(fā)表留言」必須通過(guò)用戶的「真實(shí)點(diǎn)擊」按鈕才能完成,而無(wú)法通過(guò)腳本自動(dòng)實(shí)現(xiàn)?這樣就能減緩蠕蟲(chóng)傳播速度了。
實(shí)現(xiàn)
這個(gè)想法聽(tīng)起來(lái)好像不可行。如果發(fā)表留言需要帶上用戶行為信息,那么 XSS 完全可以偽造一份行為數(shù)據(jù),后端根本無(wú)法識(shí)別。
除非,用戶在點(diǎn)擊按鈕時(shí)會(huì)產(chǎn)生一個(gè)「特殊數(shù)據(jù)」,讓后端校驗(yàn)它。
但是,XSS 也可以直接調(diào)用按鈕元素的 click 方法,這樣效果和用戶點(diǎn)擊仍然一樣。后端仍無(wú)法識(shí)別,是腳本點(diǎn)的,還是用戶點(diǎn)的。
這么看來(lái),我們只能保護(hù)好這個(gè)「按鈕元素」,讓它沒(méi)法被 XSS 訪問(wèn)到。例如,放在一個(gè)不同源的 iframe 里,這樣就和 XSS 所在的環(huán)境隔離了!
不過(guò),這樣還不夠。假如 XSS 破解了這個(gè)「特殊數(shù)據(jù)」的生成規(guī)則,那么即可自己偽造一個(gè),然后直接調(diào)用 HTTP 接口發(fā)表留言。所以,我們得找一個(gè)不可偽造的硬標(biāo)識(shí)。
事實(shí)上,有個(gè)很簡(jiǎn)單的辦法:我們干脆讓 HTTP 請(qǐng)求也通過(guò) iframe 發(fā)送。這樣,后端通過(guò) referer 即可檢測(cè)請(qǐng)求是否為 iframe 發(fā)起的。畢竟,XSS 是無(wú)法偽造 referer 的!
演示
Demo: http://www.etherdream.com/FunnyScript/anti-xssworm/
注意:這個(gè)案例不是看能不能注入 XSS,而是看能不能通過(guò)當(dāng)前頁(yè)面的 JS 自動(dòng)發(fā)留言!
另外,通過(guò)第三方服務(wù)器發(fā)表是不算的。這里為簡(jiǎn)單,省略了登錄態(tài);真實(shí)場(chǎng)合下,會(huì)話 Cookie 是 HttpOnly 的,無(wú)法被 JS 獲取到,也就無(wú)法讓第三方服務(wù)器代替發(fā)表。
細(xì)節(jié):
1. 使用者加載 safebutton.js,引入 SafeButton 類(lèi)
2. 使用者實(shí)例化 SafeButton 對(duì)象 A,創(chuàng)建出一個(gè)不同源的 iframe 作為按鈕界面
3. 用戶點(diǎn)擊 iframe 按鈕后,內(nèi)部變量 S 置為 true,同時(shí)將點(diǎn)擊消息告知主頁(yè)面(postMessage)
4. 主頁(yè)面收到消息后,讓 A 產(chǎn)生 onclick 事件
5. 使用者將 HTTP 請(qǐng)求數(shù)據(jù),通過(guò) A 的 send 方法扔給 iframe
6. iframe 校驗(yàn)內(nèi)部變量 S:若為 true,則將數(shù)據(jù)通過(guò) AJAX 發(fā)送;否則放棄
7. 服務(wù)器校驗(yàn) referer:若為 iframe 的地址,則繼續(xù)業(yè)務(wù)邏輯;否則放棄
8. iframe 收到 AJAX 返回后,將結(jié)果扔給主頁(yè)面
9. A 產(chǎn)生 onreceive 事件,其中包含 HTTP 返回結(jié)果
其中 No.6 的步驟最為關(guān)鍵。正是這一步,使得未經(jīng)用戶點(diǎn)擊,XSS 強(qiáng)制扔給 iframe 的消息變得無(wú)效!
缺陷
當(dāng)然,這個(gè)方案阻擋不了點(diǎn)擊劫持 —— XSS 可以把 iframe 元素放大至整個(gè)頁(yè)面,并設(shè)置全透明。
這樣用戶只要在頁(yè)面的任何位置點(diǎn)一下,iframe 的 S 狀態(tài)就變成 true 了,于是就能繞過(guò) No.6。
結(jié)尾
當(dāng)然,安全防御有勝于無(wú)。并且該方案的改造成本也不是很大,后端只是增加一個(gè) referer 判斷而已;前端也只需改造個(gè)別按鈕,例如發(fā)帖按鈕,像點(diǎn)贊這種按鈕就沒(méi)必要保護(hù)了。






















