轉:XSS和CSRF原理及防範

原文地址:http://www.freebuf.com/articles/web/39234.htmljavascript

 

隨着Web2.0、社交網絡、微博等等一系列新型的互聯網產品的誕生,基於Web環境的互聯網應用愈來愈普遍,企業信息化的過程當中各類應用都架設在Web平臺上,Web業務的迅速發展也引發黑客們的強烈關注,接踵而至的就是Web安全威脅的凸顯。php

黑客利用網站操做系統的漏洞和Web服務程序的SQL注入漏洞等獲得Web服務器的控制權限,輕則篡改網頁內容,重則竊取重要內部數據,更爲嚴重的則是在網頁中植入惡意代碼,使得網站訪問者受到侵害。html

現在,Web安全成爲焦點,但網站的漏洞仍是頻頻出現,在白帽子們進行網站測試時,恐怕對於SQL注入、XSS跨站、CSRF接觸最多,但對於網站的開發者們來講,對這些熟知多少?本文從開發者的角度,對於XSS和CSRF進行簡要概述。java

PART1  XSS跨站腳本(Cross-site scripting)node

XSS成因歸納 :web

    XSS其實就是Html的注入問題,攻擊者的輸入沒有通過嚴格的控制進入了數據庫,最終顯示給來訪的用戶,致使能夠在來訪用戶的瀏覽器裏以瀏覽用戶的身份執行Html代碼,數據流程以下:攻擊者的Html輸入—>web程序—>進入數據庫—>web程序—>用戶瀏覽器。ajax

檢測方法:數據庫

//一般有一些方式能夠測試網站是否有正確處理特殊字符:express

><script>alert(document.cookie)</script> ='><script>alert(document.cookie)</script> "><script>alert(document.cookie)</script> <script>alert(document.cookie)</script> <script>alert(vulnerable)</script> %3Cscript%3Ealert('XSS')%3C/script%3E <script>alert('XSS')</script> <img src="javascript:alert('XSS')"> <img src="http://xxx.com/yyy.png" onerror="alert('XSS')"> <div style="height:expression(alert('XSS'),1)" />(這個僅限 IE 有效)

攻擊手段和目的:瀏覽器

    攻擊者使被攻擊者在瀏覽器中執行腳本後,若是須要收集來自被攻擊者的數據(如cookie或其餘敏感信息),能夠自行架設一個網站,讓被攻擊者經過JavaScript等方式把收集好的數據做爲參數提交,隨後以數據庫等形式記錄在攻擊者本身的服務器上。 

a. 盜用 cookie ,獲取敏感信息。

b.利用植入 Flash ,經過 crossdomain 權限設置進一步獲取更高權限;或者利用Java等獲得相似的操做。 

c.利用 iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻擊)用戶的身份執行一些管理動做,或執行一些通常的如發微博、加好友、發私信等操做。 

d.利用可被攻擊的域受到其餘域信任的特色,以受信任來源的身份請求一些平時不容許的操做,如進行不當的投票活動。

e.在訪問量極大的一些頁面上的XSS能夠攻擊一些小型網站,實現DDoS攻擊的效果。

漏洞的防護和利用:

避免XSS的方法之一主要是將用戶所提供的內容進行過濾,許多語言都有提供對HTML的過濾:

PHP的htmlentities()或是htmlspecialchars()。
Python的cgi.escape()。
ASP的Server.HTMLEncode()。
ASP.NET的Server.HtmlEncode()或功能更強的Microsoft Anti-Cross Site Scripting Library
Java的xssprotect(Open Source Library)。
Node.js的node-validator。

使用HTTP頭指定類型:

不少時候可使用HTTP頭指定內容的類型,使得輸出的內容避免被做爲HTML解析。如在PHP語言中使用如下代碼: 

header
('Content-Type: text/javascript; charset=utf-8');

便可強行指定輸出內容爲文本/JavaScript腳本(順便指定了內容編碼),而非能夠引起攻擊的HTML。

PART2 CSRF:冒充用戶之手

示意圖:

    XSS 是實現 CSRF 的諸多途徑中的一條,但絕對不是惟一的一條。通常習慣上把經過 XSS 來實現的 CSRF 稱爲 XSRF。

    CSRF 顧名思義,是僞造請求,冒充用戶在站內的正常操做。咱們知道,絕大多數網站是經過 cookie 等方式辨識用戶身份(包括使用服務器端 Session 的網站,由於 Session ID 也是大多保存在 cookie 裏面的),再予以受權的。因此要僞造用戶的正常操做,最好的方法是經過 XSS 或連接欺騙等途徑,讓用戶在本機(即擁有身份 cookie 的瀏覽器端)發起用戶所不知道的請求。

    要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:

1.登陸受信任網站A,並在本地生成Cookie。
2.在不登出A的狀況下,訪問危險網站B。

    看到這裏,你也許會說:「若是我不知足以上兩個條件中的一個,我就不會受到CSRF的攻擊」。是的,確實如此,但你不能保證如下狀況不會發生:
1.你不能保證你登陸了一個網站後,再也不打開一個tab頁面並訪問另外的網站。
2.你不能保證你關閉瀏覽器了後,你本地的Cookie馬上過時,你上次的會話已經結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數人都會錯誤的認爲關閉瀏覽器就等於退出登陸/結束會話了……)
3.上圖中所謂的攻擊網站,多是一個存在其餘漏洞的可信任的常常被人訪問的網站。
    上面大概地講了一下CSRF攻擊的思想,下面我將用幾個例子詳細說說具體的CSRF攻擊,這裏我以一個銀行轉帳的操做做爲例子(僅僅是例子,真實的銀行網站沒這麼傻:>)

示例1:
銀行網站A,它以GET請求來完成銀行轉帳的操做,如:

http://www.mybank.com/Transfer.php?toBankId=11&money=1000

危險網站B,它裏面有一段HTML的代碼以下:

 <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

    首先,你登陸了銀行網站A,而後訪問危險網站B,噢,這時你會發現你的銀行帳戶少了1000塊……
    爲何會這樣呢?緣由是銀行網站A違反了HTTP規範,使用GET請求更新資源。在訪問危險網站B的以前,你已經登陸了銀行網站A,而B中的<img>以GET的方式請求第三方資源(這裏的第三方就是指銀行網站了,本來這是一個合法的請求,但這裏被不法分子利用了),因此你的瀏覽器會帶上你的銀行網站A的Cookie發出Get請求,去獲取資源「http://www.mybank.com/Transfer.php?toBankId=11&money=1000」,結果銀行網站服務器收到請求後,認爲這是一個更新資源操做(轉帳操做),因此就馬上進行轉帳操做……

示例2:
爲了杜絕上面的問題,銀行決定改用POST請求完成轉帳操做。
銀行網站A的WEB表單以下:  

<form action="Transfer.php" method="POST">         <p>ToBankId: <input type="text" name="toBankId" /></p>         <p>Money: <input type="text" name="money" /></p>         <p><input type="submit" value="Transfer" /></p> </form>

後臺處理頁面Transfer.php以下:

<?php       session_start();     if (isset($_REQUEST[&#039;toBankId&#039;] && isset($_REQUEST[&#039;money&#039;]))     {        buy_stocks($_REQUEST[&#039;toBankId&#039;], $_REQUEST[&#039;money&#039;]);     } >

危險網站B,仍然只是包含那句HTML代碼:

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

    和示例1中的操做同樣,你首先登陸了銀行網站A,而後訪問危險網站B,結果…..和示例1同樣,你再次沒了1000塊~T_T,此次事故的緣由是:銀行後臺使用了$_REQUEST去獲取請求的數據,而$_REQUEST既能夠獲取GET請求的數據,也能夠獲取POST請求的數據,這就形成了在後臺處理程序沒法區分這究竟是GET請求的數據仍是POST請求的數據。在PHP中,可使用$_GET和$_POST分別獲取GET請求和POST請求的數據。在JAVA中,用於獲取請求數據request同樣存在不能區分GET請求數據和POST數據的問題。 

示例3:
    通過前面2個慘痛的教訓,銀行決定把獲取請求數據的方法也改了,改用$_POST,只獲取POST請求的數據,後臺處理頁面Transfer.php代碼以下:

<?php      session_start();      if (isset($_POST['toBankId'] && isset($_POST['money']))      {         buy_stocks($_POST['toBankId'], $_POST['money']);      } ?>

然而,危險網站B與時俱進,它改了一下代碼:

<html>       <head>         <script type="text/javascript">           function steal()           {                    iframe = document.frames["steal"];                    iframe.document.Submit("transfer");           }         </script>       </head>       <body onload="steal()">         <iframe name="steal" display="none">           <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">             <input type="hidden" name="toBankId" value="11">             <input type="hidden" name="money" value="1000">           </form>         </iframe>       </body> </html>

    若是用戶還是繼續上面的操做,很不幸,結果將會是再次不見1000塊……由於這裏危險網站B暗地裏發送了POST請求到銀行!

    總結一下上面3個例子,CSRF主要的攻擊模式基本上是以上的3種,其中以第1,2種最爲嚴重,由於觸發條件很簡單,一個<img>就能夠了,而第3種比較麻煩,須要使用JavaScript,因此使用的機會會比前面的少不少,但不管是哪一種狀況,只要觸發了CSRF攻擊,後果都有可能很嚴重。
    理解上面的3種攻擊模式,其實能夠看出,CSRF攻擊是源於WEB的隱式身份驗證機制!WEB的身份驗證機制雖然能夠保證一個請求是來自於某個用戶的瀏覽器,但卻沒法保證該請求是用戶批准發送的!

如何防護?
請求令牌(一種簡單有效的防護方法):
首先服務器端要以某種策略生成隨機字符串,做爲令牌(token),保存在 Session 裏。而後在發出請求的頁面,把該令牌以隱藏域一類的形式,與其餘信息一併發出。在接收請求的頁面,把接收到的信息中的令牌與 Session 中的令牌比較,只有一致的時候才處理請求,處理完成後清理session中的值,不然返回 HTTP 403 拒絕請求或者要求用戶從新登錄驗證身份
令牌來防止 CSRF 有如下幾點要注意:
 a.雖然請求令牌原理和驗證碼有類似之處,但不該該像驗證碼同樣,全局使用一個 Session Key。由於請求令牌的方法在理論上是可破解的,破解方式是解析來源頁面的文本,獲取令牌內容。若是全局使用一個 Session Key,那麼危險係數會上升。原則上來講,每一個頁面的請求令牌都應該放在獨立的 Session Key 中。咱們在設計服務器端的時候,能夠稍加封裝,編寫一個令牌工具包,將頁面的標識做爲 Session 中保存令牌的鍵。

b.在 ajax 技術應用較多的場合,由於頗有請求是 JavaScript 發起的,使用靜態的模版輸出令牌值或多或少有些不方便。但不管如何,請不要提供直接獲取令牌值的 API。這麼作無疑是鎖上了大門,卻又把鑰匙放在門口,讓咱們的請求令牌退化爲同步令牌。

c.第一點說了請求令牌理論上是可破解的,因此很是重要的場合,應該考慮使用驗證碼(令牌的一種升級,目前來看破解難度極大),或者要求用戶再次輸入密碼(亞馬遜、淘寶的作法)。但這兩種方式用戶體驗都很差,因此須要產品開發者權衡。

d.不管是普通的請求令牌仍是驗證碼,服務器端驗證過必定記得銷燬。忘記銷燬用過的令牌是個很低級可是殺傷力很大的錯誤。咱們學校的選課系統就有這個問題,驗證碼用完並未銷燬,故只要獲取一次驗證碼圖片,其中的驗證碼能夠在屢次請求中使用(只要再也不次刷新驗證碼圖片),一直用到。

以下也列出一些聽說能有效防範 CSRF,其實效果甚微或甚至無效的作法:
a.經過 referer 斷定來源頁面:referer 是在 HTTP Request Head 裏面的,也就是由請求的發送者決定的。若是我喜歡,能夠給 referer 任何值。固然這個作法並非毫無做用,起碼能夠防小白。但我以爲性價比不如令牌。

b.過濾全部用戶發佈的連接:這個是最無效的作法,由於首先攻擊者不必定要從站內發起請求(上面提到過了),並且就算從站內發起請求,途徑也遠遠不知連接一條。好比 <img src="./create_post.php" /> 就是個不錯的選擇,還不須要用戶去點擊,只要用戶的瀏覽器會自動加載圖片,就會自動發起請求。

c.在請求發起頁面用 alert 彈窗提醒用戶:這個方法看上去能干擾站外經過 iframe 發起的 CSRF,但攻擊者也能夠考慮用 window.alert = function(){}; 把 alert 弄啞,或者乾脆脫離 iframe,使用 Flash 來達到目的。

整體來講,目前防護 CSRF 的諸多方法還沒幾個能完全無解的。 做爲開發者,咱們能作的就是儘可能提升破解難度。當破解難度達到必定程度,網站就逼近於絕對安全的位置了。

相關文章
相關標籤/搜索