yii2 CSRF验证原理详解
发布时间:2021-11-18 13:49:03 所属栏目:PHP教程 来源:互联网
导读:知识补充 因为yii2 csrf的验证的加解密 涉及到异或运算 所以需要先补充php里字符串异或运算的相关知识,不需要的可以跳过 ^异或运算 不一样返回1 否者返回 0 在PHP语言中,经常用来做加密的运算,解密也直接用^就行 字符串运算时 利用字符的ascii码转换为2进制
|
知识补充 因为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, 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比 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.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−>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和会话里的值进行比较 ![]() (编辑:云计算网_泰州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |



浙公网安备 33038102330476号