跨站請求偽造CSRF攻防

pythontab發表於2014-11-21

一.CSRF是什麼?

  CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。

二.CSRF可以做什麼?

  你這可以這麼理解CSRF攻擊:攻擊者盜用了你的身份,以你的名義傳送惡意請求。CSRF能夠做的事情包括:以你名義傳送郵件,發訊息,盜取你的賬號,甚至於購買商品,虛擬貨幣轉賬......造成的問題包括:個人隱私洩露以及財產安全。

三.CSRF漏洞現狀

  CSRF這種攻擊方式在2000年已經被國外的安全人員提出,但在國內,直到06年才開始被關注,08年,國內外的多個大型社群和互動網站分別爆出CSRF漏洞,如:NYTimes.com(紐約時報)、Metafilter(一個大型的BLOG網站),YouTube和百度HI......而現在,網際網路上的許多站點仍對此毫無防備,以至於安全業界稱CSRF為“沉睡的巨人”。

四.CSRF的原理

要完成一次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中的跨站請求偽造CSRF攻防以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['toBankId'] && isset($_REQUEST['money']))
    {
        buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
    }
  ?>

  危險網站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種最為嚴重,因為觸發條件很簡單,一個跨站請求偽造CSRF攻防就可以了,而第3種比較麻煩,需要使用JavaScript,所以使用的機會會比前面的少很多,但無論是哪種情況,只要觸發了CSRF攻擊,後果都有可能很嚴重。

  理解上面的3種攻擊模式,其實可以看出,CSRF攻擊是源於WEB的隱式身份驗證機制!WEB的身份驗證機制雖然可以保證一個請求是來自於某個使用者的瀏覽器,但卻無法保證該請求是使用者批准傳送的!

五.CSRF的防禦

CSRF 的防範機制有很多種,防範的方法也根據 CSRF 攻擊方式的不斷升級而不斷演化。常用的有檢查 Refer 頭部資訊,使用一次性令牌,使用驗證圖片等手段。出於效能的考慮,如果每個請求都加入令牌驗證將極大的增加伺服器的負擔,具體採用那種方法更合理,需要謹慎審視每種保護的優缺點。

1. 檢查 HTTP 頭部 Refer 資訊,這是防止 CSRF 的最簡單容易實現的一種手段。根據 RFC 對於 HTTP 協議裡面 Refer 的定義,Refer 資訊跟隨出現在每個 Http 請求頭部。Server 端在收到請求之後,可以去檢查這個頭資訊,只接受來自本域的請求而忽略外部域的請求,這樣就可以避免了很多風險。當然這種檢查方式由於過於簡單也有它自身的弱點:

a) 首先是檢查 Refer 資訊並不能防範來自本域的攻擊。在企業業務網站上,經常會有同域的論壇,郵件等形式的 Web 應用程式存在,來自這些地方的 CSRF 攻擊所攜帶的就是本域的 Refer 域資訊,因此不能被這種防禦手段所阻止。

b) 同樣,某些直接傳送 HTTP 請求的方式(指非瀏覽器,比如用後臺程式碼等方法)可以偽造一些 Refer 資訊,雖然直接進行頭資訊偽造的方式屬於直接傳送請求,很難跟隨傳送 cookie,但由於目前客戶端手段層出不窮,flash,javascript 等大規模使用,從客戶端進行 refer 的偽造,尤其是在客戶端瀏覽器安裝了越來越多的外掛的情況下已經成為可能了。

2. 使用一次性令牌,這是當前 Web 應用程式的設計人員廣泛使用的一種方式,方法是對於 Get 請求,在 URL 裡面加入一個令牌,對於 Post 請求,在隱藏域中加入一個令牌。這個令牌由 server 端生成,由程式設計人員控制在客戶端傳送請求的時候使請求攜帶本令牌然後在 Server 端進行驗證。但在令牌的設計上目前存在著幾個錯誤的方案:

a) 使用和 Session 獨立的令牌生成方式。這種令牌的值和 Session 無關,因此容易被其他使用者偽造。這裡的其他使用者指的是當前 Web 應用程式的其他使用者和活躍在網路傳輸階段各個設定上的監聽者,這種惡意使用者可能使用自己的令牌來進行替換以便達到偽造的目的。

b) 完全使用 Session 認證資訊作為令牌的生成方式。這種保護方式對於保護 CSRF 是起了作用的,但是可能會造成其他危害,具體來說,如果某些 URL 或者網頁被複製下來與其他人共享,那麼這些 URL 或者複製下來的網頁中可能會含有使用者的會話資訊,這種資訊一旦被惡意使用者獲得,就能造成極大的危害。

因此,一個正確的令牌設計應該是使用 Session 資訊做 Hash,用得出的雜湊值來做 CSRF 的令牌。

3. 使用驗證圖片,這種方法的出現的作用是對於機器人暴力攻擊的防止。但在 CSRF 的防範上,也有一些安全性要求比較高的的應用程式結合驗證圖片和一次性令牌來做雙重保護。由於這種圖片驗證資訊很難被惡意程式在客戶端識別,因此能夠提高更強的保護。當客戶端的瀏覽器可能已經處於一種不安全的環境中的情況下(比如客戶端的安全級別設定較低,客戶端瀏覽器安裝了不安全的外掛等)。

以上給的這些只是防範 CSRF 的比較通用的一些方法,Web 開發人員可以根據自己對自己的應用程式的功能的理解來確定安全級別的要求從而選擇使用不同的保護措施,也推薦在同一應用程式內部結合使用多種方法來進行保護。

注:加防也會極大的影響效能,就如在高速路上放一個收費站,建議只在重要操作上加防。如位慎重考慮。


相關文章