淺談csrf攻擊以及yii2對其的防範措施

凡是我yii2學習社羣的成員都知道,我不止一次給你們說構造表單100%使用yii2的ActiveForm來實現,這除了能和AR更好結合外就是自動生成csrf隱藏域,一個很是安全的舉措。php

今天北哥就給你們普及下csrf是啥?若是你已經知道了能夠直接拉文章到底部點個贊。:smile:html

CSRF(Cross-site request forgery跨站請求僞造)是一種對網站的惡意利用,在 2007 年曾被列爲互聯網 20 大安全隱患之一。程序員

關於CSRF,要從一個故事開始~web

老王丟錢事件

這個故事要從程序員老王丟了1萬塊錢提及,總之是進了小偷,找回無果。丟錢後的老王一直在思考,錢是怎麼丟的、爲什麼丟錢、爲什麼是我丟錢~~ajax

後來老王出現了嚴重的心理問題,他決定報復社會。瀏覽器

老王首先研究了網銀系統,他發現轉帳是經過GET形式安全

https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=abei
複製代碼

這意思就是說將 liuxiaoer 的1000元錢轉給abei,固然當請求到達銀行服務器後,程序會驗證該請求是否來自合法的session而且該session的用戶就是 liuxiaoer 而且已經登陸。服務器

老王本身也有一個銀行帳號 wang2,他嘗試登陸而且經過瀏覽器發送請求給銀行,代碼以下yii2

https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2
複製代碼

失敗了~由於當前登陸帳號是老王本身,發送請求後服務器發現session所屬用戶wang2和account=liuxiaoer並非一我的,所以被拒絕。cookie

也就是說這個操做必須是 liuxiaoer 本身才能夠,復仇的力量是可怕的,老王經過facebook層層找到了 liuxiaoer 就是快遞員老劉的銀行帳號。

因而一個偉大的計劃誕生了,老王的計劃是這樣的。

一、首先作一個網頁,在網頁中加入以下代碼

src="https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2"
複製代碼

而後經過各類情景讓老劉(liuxiaoer)訪問了此網頁。

二、當老劉(liuxiaoer)訪問此網頁後,上面的請求會被髮送到銀行,此刻還會帶着老劉(liuxiaoer)本身的瀏覽器cookie信息,固然這樣通常也不會成功,由於銀行服務器發現老劉(liuxiaoer)並不在登陸狀態。

三、老王想了一個招,他在淘寶找了一個灰色商人老李,讓他經過種種方法,總之讓老劉(liuxiaoer)經過瀏覽器給老李轉了一次款。

四、就在第三步操做的2分鐘內,老王成功讓老劉(liuxiaoer)再一次訪問了本身作的網頁,你知道的,此刻老劉(liuxiaoer)在銀行的session尚未過時,老王網頁給銀行服務器發送請求後,驗證經過,打款成功。

五、老王收到了款,老劉(liuxiaoer)並不知道這一切,對於銀行來講這是一筆在正常不過的轉帳。

這就是CSRF攻擊,瀏覽器沒法攔截。

CSRF攻擊特色

基於上面血淋淋的故事,咱們總結下CSRF攻擊的幾個特色。

  • 黑客藉助於受害者的cookie等瀏覽器信息騙取服務器新人,黑客並拿不到cookie等。
  • 因爲瀏覽器同源策略,黑客沒法拿到攻擊的響應結果,能作的只是發起請求,你是否還記得不少釣魚網站都模擬了登陸框麼?
  • CSRF攻擊主要是發送修改數據請求。

CSRF防護對象

所以咱們要保護的是全部能引發數據變化的客戶端請求,好比新建、更新和刪除。

CSRF防護方案

基於CSRF攻擊特色,在業界目前防護 CSRF 攻擊主要有三種策略:

  • 驗證 HTTP Referer 字段;
  • 在請求地址中添加 token 並驗證;
  • 在 HTTP 頭中自定義屬性並驗證。

HEEP Referer

在http請求的時候,頭部有一個叫作Referer的字段,該字段記錄本次請求的來源地址。所以服務器端能夠經過此字段是否爲同一個域名來判斷請求是否合法,由於客戶本身作的網頁發起的請求,其Referer爲黑客網站。

這種方法最簡單,而且不須要修改業務代碼,咱們只須要對到達服務器的每一個請求作一次攔截分析便可。

可是此方法的缺點也是明顯的,由於Referer的值是瀏覽器的,雖然HTTP協議不容許去修改,可是若是瀏覽器自身存在漏洞,那麼就有可能致使Referer被人工設置,不安全。

好比IE6,就能夠經過方法篡改Referer值。

就算是最新的瀏覽器此方法也不是絕對可用的,這涉及了用戶的隱私,不少用戶會設置瀏覽器不提供Referer,所以服務器在得不到Referer的狀況下不能貿然的決絕服務,有可能這是一個合法請求。

添加Token

CSRF攻擊之因此能成功,是由於黑客徹底僞造了一次用戶的正常請求(這也是瀏覽器沒法攔截的緣由),而且cookie信息就是用戶本身的,那麼咱們若是在請求中放入一些黑客沒法去僞造的信息(不存在與cookie中),不就能夠抵禦了麼!

好比在請求前生成一個token放到session中,當請求發生時,將token從session拿出來和請求提交過來的token進行對比,若是相等則認證經過,不然拒絕。token時刻在變化,黑客沒法去僞造。

針對於不一樣類型的請求通常方案是

  • GET 放到url中,好比http://url?csrftoken=xxxx
  • POST 放到表單的隱藏域

對於GET請求,這裏有一點要說明,在一個網站中請求的url不少,通常狀況咱們是經過js對dom的全部節點進行遍歷,發現a連接就在其href中增長token。

這裏存在一個問題,好比黑客將本身網站的連接發到了要攻擊頁面,則黑客網站連接後面會有一個token,此刻客戶能夠經過編寫本身網站代碼獲得這個token,而後用這個token馬上構造表單,發起CSRF攻擊,所以在js遍歷的時候,若是發現不是本站的連接,能夠不加token。

在HTTP頭部增長屬性

這個方法在思路上和上面的token方式同樣,只不過將token放到了HTTP頭部中,再也不參數傳遞,經過XMLHttpRequest類能夠一次性的給全部請求加上csrftoken這個HTTP頭屬性並設置值。

這種方法適合上面批量添加token不方便的狀況,一次性操做,不過侷限性也比較大,XMLHttpRequest請求一般用在ajax方法中,並不是全部請求都適合。

Yii2

首先要說的是每種CSRF防範措施都有其弊端,不管你的防範多麼嚴密,黑客擁有更多的攻擊手段,所以在重要邏輯上(必須寫入和刪除)必須很是當心,接下來咱們把yii2框架在csrf上的部署說一下。

咱們以yii2.0.14爲解說版本。

在CSRF這塊,yii2框架採起了HTTP頭部和參數token並行的方式,針對於每一個請求,在beforeAction都會作一次判斷,以下

// vendor/yiisoft/yii2/web/Controller.php
public function beforeAction($action) {
    
    if (parent::beforeAction($action)) {
        if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
            throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
        }

        return true;
    }

    return false;
}
複製代碼

若是咱們沒有設置 enableCsrfValidation 爲false,而且沒有報錯,則會進行csrf驗證,核心方法就是

Yii::$app->getRequest()->validateCsrfToken()
複製代碼

該方法存在於 vendor/yiisoft/yii2/web/Request.php 中,咱們看一看它。

public function validateCsrfToken($clientSuppliedToken = null) {
	// 省略上面代碼
    return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
        || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
}
複製代碼

validateCsrfToken函數代碼咱們只須要看最後的返回,getBodyParam或getCsrfTokenFromHeader方法獲得的token,只要有一種驗證經過,就認爲合法。

以上是總體的思路,爲了讓你看的更清晰,我畫一個圖並增長一些名詞解釋。

以上是yii2的csrf策略部署,固然我仍是推薦你使用 xdebug等調試工具 一步一步看看這個過程。

最後我在把上圖的關鍵函數進行說明

  • generateCsrfToken() 該函數生成token並存到cookie或session中,該值不會隨頁面刷新而變化,它更多充當鑰匙的做用,根絕它生成具體的csrfToken。
  • getCsrfToken() 生成具體的csrfToken,就是你在表單隱藏域中看到的那個值,這個值未來會傳到服務器和真實的csrfToken進行對比,驗證是否合法。
  • validateCsrfToken() 進行合法性驗證,該函數獲得一個真實的csrfToken而後和客戶端上傳來的csrfToken進行對比。

小結

原本想來個很高逼格的小結,想一想算了,華麗的詞彙不以下一篇靠譜的乾貨實在,等我下一篇哈。

更多現代化 PHP 知識,請前往 Yii / PHP 學習社羣