Yii2框架的csrf验证原理分析及token缓存解决方案-php教程

资源魔 37 0
本文次要分三个局部,起首简略引见csrf,接着对照源码重点剖析一下yii框架的验证原理,最初针对页面缓存招致的token被缓存提出一种可行的计划。触及的常识点会作为附录附于文末。感兴味的冤家理解一下吧。

1.CSRF形容

CSRF全称为“Cross-Site Request Forgery”,是正在用户非法的SESSION内发动的攻打。黑客经过正在网页中嵌入Web歹意申请代码,并诱使受益者拜访该页面,当页面被拜访后,申请正在受益者没有知情的状况下以受益者的非法身份发动,并执行黑客所等待的举措。如下HTML代码提供了一个“删除了产物”的性能:

<a href="http://www.shop.com/delProducts.php?id=100" "javascript:return confirm('Are you sure?')">Delete</a>

假定顺序员正在后盾不对该“删除了产物”申请做相应的非法性验证,只需用户拜访了该链接,相应的产物即被删除了,那末黑客可经过诈骗受益者拜访带有如下歹意代码的网页,便可正在受益者没有知情的状况下删除了相应的产物。

2.yii的csrf验证原理 /vendor/yiisoft/yii2/web/Request.php简写为Request.php

/vendor/yiisoft/yii2/web/Controller.php简写为Controller.php

开启csrf验证

正在管制器里将enableCsrfValidation为true,则管制器内一切操作城市开启验证,通常做法是将enableCsrfValidation为false,而将一些敏感操作设为true,开启部分验证。

public $enableCsrfValidation = false;
/**
 * @param \yii\base\Action $action
 * @return bool
 * @desc: 部分开启csrf验证(首要的表单提交必需退出验证,退出$accessActions便可
 */
public function beforeAction($action){
    $currentAction = $action->id;
    $accessActions = ['vote','like','delete','download'];
    if(in_array($currentAction,$accessActions)) {
        $action->controller->enableCsrfValidation = true;
    }
    parent::beforeAction($action);
    return true;
}

天生token字段

正在Request.php

起首经过平安组件Security猎取一个32位的随机字符串,并存入cookie或session,这是原生的token.

/**
 * Generates  an unmasked random token used to perform CSRF validation.
 * @return string the random token for CSRF validation.
 */
protected function generateCsrfToken()
{
    $token = Yii::$app->getSecurity()->generateRandomString();
    if ($this->enableCsrfCookie) {
        $cookie = $this->createCsrfCookie($token);
        Yii::$app->getResponse()->getCookies()->add($cookie);
    } else {
        Yii::$app->getSession()->set($this->csrfParam, $token);
    }
    return $token;
}

接着经过一系列加密交换操作,天生加密后_csrfToken,这个是传给阅读器的token. 先随机孕育发生CSRF_MASK_LENGTH(Yii2里默许是8位)长度的字符串 mask

对mask以及token进行以下运算 str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); $this->xorTokens($arg1,$arg2) 是一个先补位异或运算

/**
 * Returns the XOR result of two strings.
 * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
 * @param string $token1
 * @param string $token2
 * @return string the XOR result
 */
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;
}
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;
}

验证token

正在controller.php里挪用request.php里的validateCsrfToken办法

/**
 * @inheritdoc
 */
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;
}
public function validateCsrfToken($token = null)
{
    $method = $this->getMethod();
    if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
        return true;
    }

    $trueToken = $this->loadCsrfToken();//假如开启了enableCsrfCookie,CsrfToken就从cookie里取,否者从session里取(更平安)

    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)

而后是validateCsrfTokenInternal

private function validateCsrfTokenInternal($token, $trueToken)
{
    if (!is_string($token)) {
        return false;
    }
    $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 = $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)

3.token缓存的处理计划

当页面全体被缓存后,token也被缓存招致验证失败,一种常见的处理思绪是每一次提交前从新猎取token,这样就能够经过验证了。

附录:

str_pad(),该函数前往 input 被从左端、右端或许同时两端被填充到制订长度后的后果。假如可选的 pad_string 参数不被指定,input 将被空格字符填充,不然它将被 pad_string 填充到指定长度;

str_shuffle() 函数打乱一个字符串,应用任何一种可能的排序计划。

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

以是需求先增补php里字符串异或运算的相干常识,没有需求的能够跳过

^异或运算 纷歧样前往1 否者前往 0 正在PHP言语中,常常用来做加密的运算,解密也间接用^就行 字符串运算时 行使字符的ascii码转换为2进制来运算 单个字符运算

1.关于单个字符以及单个字符的 间接较量争论其后果便可 比方内外的a^b

2.关于长度同样的多个字符串 如内外的ab^cd 较量争论a^c对应的后果以及以及b^d对应的后果 对应的字符衔接起来

相干教程:PHP视频教程

以上就是Yii2框架的csrf验证原理剖析及token缓存处理计划的具体内容,更多请存眷资源魔其它相干文章!

标签: php开发教程 php开发资料 php开发自学 yii框架 csrf验证 token缓存

抱歉,评论功能暂时关闭!