yii2 csrf验证原理分析

yii2 csrf验证原理剖析,加解密原理剖析php

知识补充html

由于yii2 csrf的验证的加解密 涉及到异或运算web

因此须要先补充php里字符串异或运算的相关知识,不须要的能够跳过安全

^异或运算
不同返回1 否者返回 0
在PHP语言中,常常用来作加密的运算,解密也直接用^就行
字符串运算时 利用字符的ascii码转换为2进制来运算
单个字符运算
举例的ascii见下表yii2

字符cookie

二进制session

ASCIIapp

adom

1100001yii

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 "[object Object]";for($i=0;$i<strlen($r) ;$i++){    echo ord($r[$i])."
";
}?>

对于不等的
以短的字符串长度位进行计算

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

<?phppublic 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里

<?phppublic 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位)长度的字符串 mask3.对mask和token进行以下运算str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));$this->xorTokens($arg1,$arg2) 是一个先补位异或运算

 

传入$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比$arg2短,$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->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 . $this->xorTokens($token, $mask)));
解密
1.首先要把.替换成+
2.而后base64_decode
再 根据长度分别取出$mask和$this->xorTokens($token, $mask) ;
为了说明方便 $this->xorTokens($token, $mask) 这里称做 token1
而后
进行mask和token1的异或运算,即得token
注意在加密时
token1=token^mask
因此
解密时
token=mask^token1=mask^(token^mask)

yii2中的核心思路token是从会话中取得的用随机串和token进行运算处理 获得一个加密串验证的时候经过这个加密串解密出来这个token和会话里的值进行比较

相关文章
相关标签/搜索