歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> yii2 CSRF驗證原理分析

yii2 CSRF驗證原理分析

日期:2017/3/1 9:09:20   编辑:Linux編程

知識補充

因為yii2 csrf的驗證的加解密 涉及到異或運算

所以需要先補充php裡字符串異或運算的相關知識,不需要的可以跳過

^異或運算
不一樣返回1 否者返回 0
在PHP語言中,經常用來做加密的運算,解密也直接用^就行
字符串運算時 利用字符的ascii碼轉換為2進制來運算
單個字符運算
舉例的ascii見下表

字符

二進制

ASCII

a

1100001

97

b

1100010

98

c

1100011

99

d

1100100

100

計算結果

運算

二進制

ASCII

a^b

0000 0011

3

a^c

0000 0010

2

b^d

0000 0110

6

ab^cd

0000 0010

2

a^cd

0000 0010

2

ab^c

0000 0010

2

1.對於單個字符和單個字符的
直接計算其結果即可 比如表裡的a^b

2.對於長度一樣的多個字符串 如表裡的ab^cd
計算a^c對應的結果和和b^d對應的結果 對應的字符連接起來

<?php
$str1='ab';
$str2="cd";
$r= $str1^$str2;
var_dump($r);
echo "<hr>";
for($i=0;$i<strlen($r) ;$i++){
    echo ord($r[$i])."<br>";
}
?>

對於不等的
以短的字符串長度位進行計算

Yii2的csrf token驗證
在yii2的接收post請求時
在如果開啟
enableCsrfValidation為true
在/vendor/yiisoft/yii2/web/Controller.php

<?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;
    }
?>

會進行validateCsrfToken驗證
在/vendor/yiisoft/yii2/web/Request.php

<?php
public function validateCsrfToken($token = null)
    {

        $method = $this->getMethod();
        // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
            return true;
        }

        $trueToken = $this->loadCsrfToken();


        if ($token !== null) {
            return $this->validateCsrfTokenInternal($token, $trueToken);
        } else {
            return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
                || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
        }
    }
?>

說明在 GET, HEAD, OPTIONS 均不驗證,除了這幾種主要用的也就post了

說明在我們發送post請求時必須發送相關驗證的字段和值
下面看CsrfToken產生過程
在/vendor/yiisoft/yii2/web/Request.php裡

<?php
public function getCsrfToken($regenerate = false)
    {

        if ($this->_csrfToken === null || $regenerate) {
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
                $token = $this->generateCsrfToken();
            }
            // the mask doesn't need to be very random
            $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
            $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH);
            // The + sign may be decoded as blank space later, which will fail the validation
            $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
        }
        return $this->_csrfToken;
    }
?>

會發現
_csrfToken的產生大致如下
如果開啟了enableCsrfCookie,
CsrfToken就從cookie裡取,否者從session裡取(更安全)
可在
/vendor/yiisoft/yii2/web/Request.php的下面部位看到

<?php
 protected function loadCsrfToken()
    {
        if ($this->enableCsrfCookie) {
            return $this->getCookies()->getValue($this->csrfParam);
        } else {
            return Yii::$app->getSession()->get($this->csrfParam);
        }
    }
?>

從loadCsrfToken()裡取出的值這裡稱token

在post裡發送的也就是Yii::$app->getRequest()->csrfParam 這裡稱csrfToken
現在根據代碼大致說下生成和驗證的主要思路,當然自己看代碼更能細致的了解
1.從cookie或者session裡取出token ,當然cookie或者session裡如果沒有就是初始化操作的過程了,這裡初始化不是重點
2.隨機產生CSRF_MASK_LENGTH(Yii2裡默認是8位)長度的字符串 mask
3.對mask和token進行如下運算
str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));

$this->xorTokens($arg1,$arg2) 是一個先補位異或運算 

傳入arg1, arg1, arg2
長度短的要用自身補到長度長的字符串的位置
見代碼部分
在 /vendor/yiisoft/yii2/web/Request.php 的如下部分

 <?php
  private function xorTokens($token1, $token2)
    {
        $n1 = StringHelper::byteLength($token1);
        $n2 = StringHelper::byteLength($token2);
        if ($n1 > $n2) {
            $token2 = str_pad($token2, $n1, $token2);
        } elseif ($n1 < $n2) {
            $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1);
        }

        return $token1 ^ $token2;
    }
 ?>

就是說如果 arg1比 arg1比 arg2短,arg1要用自身補齊補到和和 arg1要用自身補齊補到和和 arg2一樣的長度
這裡為什麼要這樣做?
因為在php裡
'a'^'bc' 會只算 a^b 而不考慮c了,這裡采用了向長度更長的來補
如果用
xorTokens來處理 'a'和'bc'
會先把a用自己填充到和bc一樣的長度後再進行異或運算
異或運算詳見上文補充

str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));


計算後即會得出在post請求時要發送的值 csrfToken

下面是驗證過程
1.根據 表單字段名
Yii::$app->getRequest()->csrfParam;
從post裡拿到
csrfToken的值
從方法 validateCsrfToken裡可以看到
代碼
在/vendor/yiisoft/yii2/web/Request.php 的如下部分

<?php
 public function validateCsrfToken($token = null)
    {

        $method = $this->getMethod();
        // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
            return true;
        }

        $trueToken = $this->loadCsrfToken();


        if ($token !== null) {
            return $this->validateCsrfTokenInternal($token, $trueToken);
        } else {
            return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
                || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
        }
    }

?>

this−>getBodyParam( this−>getBodyParam( this->csrfParam)
可以看出
解密的目的就是要從
csrfToken裡取出token 然後和會話裡的token比較
見/vendor/yiisoft/yii2/web/Request.php 的如下部分

<?php
 private function validateCsrfTokenInternal($token, $trueToken)
    {

        $token = base64_decode(str_replace('.', '+', $token));
        $n = StringHelper::byteLength($token);
        if ($n <= static::CSRF_MASK_LENGTH) {
            return false;
        }
        $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH);
        $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH);
        /*
          注意此時的$token在加密過程中是xorTokens($trueToken,$mask)的結果
        */
        $token = $this->xorTokens($mask, $token);

        return $token === $trueToken;
    }
?>

加密時用的是
str_replace('+', '.', base64_encode(mask. mask. this->xorTokens(token, token, mask)));
解密
1.首先要把.替換成+
2.然後base64_decode
再 根據長度分別取出mask和 mask和 this->xorTokens(token, token, mask) ;
為了說明方便 this−>xorTokens( this−>xorTokens( token, $mask) 這裡稱作 token1
然後
進行mask和token1的異或運算,即得token
注意在加密時
token1=token^mask
所以
解密時
token=mask^token1=mask^(token^mask)

yii2
中的核心思路
token是從會話中取得的
用隨機串和token進行運算處理 得到一個加密串
驗證的時候通過這個加密串解密出來這個token和會話裡的值進行比較

在Yii2中使用Pjax導致Yii2內聯腳本載入失敗的問題 http://www.linuxidc.com/Linux/2016-03/128949.htm

Yii2 實現修改密碼功能 http://www.linuxidc.com/Linux/2015-07/120137.htm

Yii 用戶登陸機制 http://www.linuxidc.com/Linux/2015-01/111602.htm

Yii中引入js和css文件 http://www.linuxidc.com/Linux/2015-01/111603.htm

Yii 不完全解決方案 http://www.linuxidc.com/Linux/2015-01/111606.htm

Yii CGridView 基本使用 http://www.linuxidc.com/Linux/2015-01/111607.htm

Yii框架分布式緩存的實現方案 http://www.linuxidc.com/Linux/2015-02/113828.htm

Yii 的詳細介紹:請點這裡
Yii 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved