• yii2的防御csrf攻击机制


    csrf,中文名称:跨站请求伪造,可以在百度上搜索资料,详细了解这一方面的概念。对于我们是非常有帮助的。
    yii2的csrf的实现功能是在yiiweb equest类实现功能的。
    request类中的属性,默认是true的。
    public $enableCsrfValidation = true;
    所以我们在配置文件中的request组件中可以配置该值
    request => [
    'enableCookieValidation' => true,
    ]
    这是全局有效的,也就是说每一个post的请求,都会启用csrf的防御攻击的功能,即进行验证。
    简单的说,整个访问策略如下:

    (1)通过Yii::$app->request->csrfToken 第一次访问获取csrfToken时,直接到getCsrfToken()访问

    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;
    }
    

     每一次访问$this->_csrfToken都会等于null,$regenerate 默认等于false。所以接着执行$token = $this->loadCsrfToken()这个函数。

    protected function loadCsrfToken()
        {
            if ($this->enableCsrfCookie) {
                return $this->getCookies()->getValue($this->csrfParam);
            } else {
                return Yii::$app->getSession()->get($this->csrfParam);
            }
    }
    

     去cookie中获取$_COOKIE['_csrf']这个token,由于第一次访问这个token肯定不会存在,故返回null。所以就会去执行$this->generateCsrfToken()。

    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;
    }
    

     这个函数就是随意创建一个token字符串,然后将它保存在$_COOKIE['_csrf']中。这样子在网站的根目录/,COOKIE就存在了这个token了,并且返回这个token。只要我们没有关闭整个网页,那么这个$_COOKIE['_csrf']的值就不会变,也就代表本机客户端的唯一凭证。
    接着再看一下getCsrfToken()函数里的这几行代码:
    $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)));
    这里是利用token和字符串,通过64位进行编码加密生成_csrfToken并且返回,也就是获取csrfToken这个值了。
    (2)第二次访问时,Yii::$app->request->csrfToken,由于$token = $this->loadCsrfToken()这个函数访问已经可以获取到token,也就是获取网站的$_COOKIE['_csrf']的值,所以不会再次重新生成的,所以接着进行64位编码加密生成_csrfToken并且返回。
    (3)那么我们需要将数据post过去的时候,我们会在yiiwebconreoller的类中的beforeAction($action)函数进行验证

    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;
    }
    

     通过Yii::$app->getRequest()->validateCsrfToken()这个函数验证

    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();
     
            var_dump($trueToken);
     
            if ($token !== null) {
                return $this->validateCsrfTokenInternal($token, $trueToken);
            } else {
                // 只要有一个为真,则返回真
                return ($this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken));
            }
    }
    

     返回true代表认证通过,false代表失败,对于GET,HEAD', 'OPTIONS',这种方式是不认证的,返回true,默认通过,可以继续访问。

    如果是其他的访问方式,例如POST,那就的认证。
    $trueToken = $this->loadCsrfToken();这个获取完整COOKIE['_csrf']的真实存在的token。

    看看$this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken);
    里面的这一句$this->getBodyParam($this->csrfParam)。就是获取post过来的csrfToken的值或者表单的值,然后validateCsrfTokenInternal($token, $trueToken),这个函数将csrfToken进行解密(因为之前通过Yii::$app->request->csrfToken这个值的时候是加密的了,所以现在要解密)。解密之后的值如果和$trueToken相同的话就返回true。

    再看看$this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken)这一句是通过$this->getCsrfTokenFromHeader()获取head中的csrfToken的值,再进行解密,解密之后的值如果和$trueToken相同的话就返回true。

     注意的是return ($this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken));
     这一句是判断($this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken))通过解密认证后就等效于return (true || false)的模式,也就是通过||判定括号里的真假,只要有一个true,则返回true

    所以说一旦生成token并保存在COOKIE['_csrf']中,那么每一次在访问时,就会以这个token作为一个基准进行数据加密随意生成一个csrfToken,然后返回给表单中。当post数据过来的时候,就得将这个csrfToken传递过来,然后进行解密,再和COOKIE['_csrf']的token进行比较,那么如果相等就说明访问是无攻击性的,是本站的访问。如果访问不通过,说明可能删改了一些信息,是不安全的。

  • 相关阅读:
    C#+API实现指定窗体激活
    DEVC++学习之(一)
    javascript 实现原生下载的各种情况
    IssueVision 之WebService安全篇
    Add relationship to BS sample
    ExpandRelationWithCtxt 与 GetRelatedObjects 的区别
    C#调用javascript
    解禁网页限制
    Unix cc options vs gcc options
    IssueVision 之模式篇
  • 原文地址:https://www.cnblogs.com/niuben/p/11057406.html
Copyright © 2020-2023  润新知