加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > PHP > 正文

yii2的csrf验证原理分析及token缓存解决方案

发布时间:2020-05-31 12:32:12 所属栏目:PHP 来源:互联网
导读:本文章向大家介绍yii2的csrf验证原理分析及token缓存解决方案,需要的朋友可以参考一下

本文主要分三个部分,首先简单介绍csrf,接着对照源码重点分析一下yii框架的验证原理,最后针对页面缓存导致的token被缓存提出一种可行的方案。涉及的知识点会作为附录附于文末。

1.CSRF描述

CSRF全称为“Cross-Site Request Forgery”,是在用户合法的SESSION内发起的攻击。黑客通过在网页中嵌入Web恶意请求代码,并诱使受害者访问该页面,当页面被访问后,请求在受害者不知情的情况下以受害者的合法身份发起,并执行黑客所期待的动作。以下HTML代码提供了一个“删除产品”的功能:

Delete

假设程序员在后台没有对该“删除产品”请求做相应的合法性验证,只要用户访问了该链接,相应的产品即被删除,那么黑客可通过欺骗受害者访问带有以下恶意代码的网页,即可在受害者不知情的情况下删除相应的产品。

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 yiibaseAction $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)),static::CSRF_MASK_LENGTH);

// The + sign may be decoded as blank space later,which will fail the validation

$this->_csrfToken = str_replace('+',$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,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,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对应的结果 对应的字符连接起来

(编辑:安卓应用网)

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

    推荐文章
      热点阅读