Welcome 微信登录

首页 / 操作系统 / Linux / yii2 CSRF验证原理分析

知识补充因为yii2 csrf的验证的加解密 涉及到异或运算所以需要先补充php里字符串异或运算的相关知识,不需要的可以跳过^异或运算
不一样返回1 否者返回 0
在PHP语言中,经常用来做加密的运算,解密也直接用^就行
字符串运算时 利用字符的ascii码转换为2进制来运算
单个字符运算
举例的ascii见下表
字符二进制ASCII
a110000197
b110001098
c110001199
d1100100100
计算结果
运算二进制ASCII
a^b0000 00113
a^c0000 00102
b^d0000 01106
ab^cd0000 00102
a^cd0000 00102
ab^c0000 00102
1.对于单个字符和单个字符的
直接计算其结果即可 比如表里的a^b2.对于长度一样的多个字符串 如表里的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<?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.1if (!$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,  arg1, arg2
长度短的要用自身补到长度长的字符串的位置
见代码部分
在 /vendor/yiisoft/yii2/web/Request.php 的如下部分 <?phpprivate 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.1if (!$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和会话里的值进行比较
在Yii2中使用Pjax导致Yii2内联脚本载入失败的问题 http://www.linuxidc.com/Linux/2016-03/128949.htmYii2 实现修改密码功能 http://www.linuxidc.com/Linux/2015-07/120137.htmYii 用户登陆机制  http://www.linuxidc.com/Linux/2015-01/111602.htmYii中引入js和css文件  http://www.linuxidc.com/Linux/2015-01/111603.htmYii 不完全解决方案  http://www.linuxidc.com/Linux/2015-01/111606.htmYii CGridView 基本使用 http://www.linuxidc.com/Linux/2015-01/111607.htmYii框架分布式缓存的实现方案 http://www.linuxidc.com/Linux/2015-02/113828.htmYii 的详细介绍:请点这里
Yii 的下载地址:请点这里本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-11/137225.htm