首页 > 编程 > PHP > 正文

yii2 csrf验证理分析

2020-03-22 20:12:51
字体:
来源:转载
供稿:网友
  • 知识补充

    因为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   html' target='_blank'>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和会话里的值进行比较

    PHP编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表