Yii2框架的csrf验证原理分析及token缓存解决方案
发布:smiling 来源: PHP粉丝网 添加日期:2020-04-05 17:35:19 浏览: 评论: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对应的结果 对应的字符连接起来
Tags: Yii2框架 csrf验证原理 token
相关文章
- ·列举PHP的Yii 2框架的开发优势(2021-06-08)
- ·yii2框架中使用下拉菜单的自动搜索yii-widget-select2实例分析(2021-07-05)
- ·Yii2框架引用bootstrap中日期插件yii2-date-picker的方法(2021-07-05)
- ·Yii2框架实现登录、退出及自动登录功能的方法详解(2021-08-16)
- ·Yii2框架类自动加载机制实例分析(2021-09-16)
- ·Yii2框架数据验证操作实例详解(2021-09-16)
- ·Yii2框架实现登陆添加验证码功能示例(2021-10-16)
- ·Yii2框架redis基本应用示例(2021-10-16)
- ·记录Yii2框架开发微信公众号遇到的问题及解决方法(2021-10-19)
- ·详解在YII2框架中使用UEditor编辑器发布文章(2021-11-01)
- ·Laravel5.4简单实现app接口Api Token认证方法(2021-12-13)
- ·Laravel的Auth验证Token验证使用自定义Redis的例子(2021-12-25)
- ·Laravel实现ApiToken认证请求(2022-01-02)
推荐文章
热门文章
最新评论文章
- 写给考虑创业的年轻程序员(10)
- PHP新手上路(一)(7)
- 惹恼程序员的十件事(5)
- PHP邮件发送例子,已测试成功(5)
- 致初学者:PHP比ASP优秀的七个理由(4)
- PHP会被淘汰吗?(4)
- PHP新手上路(四)(4)
- 如何去学习PHP?(2)
- 简单入门级php分页代码(2)
- php中邮箱email 电话等格式的验证(2)