• yii2 csrf验证原理分析


    知识补充

    因为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,$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和会话里的值进行比较

  • 相关阅读:
    yii2 gii 命令行自动生成控制器和模型
    控制器中的方法命名规范
    Vue Property or method "" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based
    IDEA插件:GsonFormat
    Spring Boot : Access denied for user ''@'localhost' (using password: NO)
    Typora添加主题
    Git基础命令图解
    Java Joda-Time 处理时间工具类(JDK1.7以上)
    Java日期工具类(基于JDK1.7版本)
    Oracle SQL Developer 连接Oracle出现【 状态: 失败 -测试失败: ORA-01017: invalid username/password; logon denied】
  • 原文地址:https://www.cnblogs.com/HKUI/p/6068453.html
Copyright © 2020-2023  润新知