加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

yii2 CSRF验证原理详解

发布时间:2021-11-18 13:49:03 所属栏目:PHP教程 来源:互联网
导读:知识补充 因为yii2 csrf的验证的加解密 涉及到异或运算 所以需要先补充php里字符串异或运算的相关知识,不需要的可以跳过 ^异或运算 不一样返回1 否者返回 0 在PHP语言中,经常用来做加密的运算,解密也直接用^就行 字符串运算时 利用字符的ascii码转换为2进制

知识补充
 
因为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和会话里的值进行比较

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读