浅谈csrf攻击以及yii2对其的防范措施

凡是我yii2学习社群的成员都知道,我不止一次给你们说构造表单100%使用yii2的ActiveForm来实现,这除了能和AR更好结合外就是自动生成csrf隐藏域,一个很是安全的举措。

今天北哥就给你们普及下csrf是啥?若是你已经知道了能够直接拉文章到底部点个赞。php

CSRF(Cross-site request forgery跨站请求伪造)是一种对网站的恶意利用,在 2007 年曾被列为互联网 20 大安全隐患之一。html

关于CSRF,要从一个故事开始~程序员

老王丢钱事件

这个故事要从程序员老王丢了1万块钱提及,总之是进了小偷,找回无果。丢钱后的老王一直在思考,钱是怎么丢的、为什么丢钱、为什么是我丢钱~~web

后来老王出现了严重的心理问题,他决定报复社会。ajax

老王首先研究了网银系统,他发现转帐是经过GET形式浏览器

https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=abei

这意思就是说将 liuxiaoer 的1000元钱转给abei,固然当请求到达银行服务器后,程序会验证该请求是否来自合法的session而且该session的用户就是 liuxiaoer 而且已经登陆。安全

老王本身也有一个银行帐号 wang2,他尝试登陆而且经过浏览器发送请求给银行,代码以下服务器

https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2

失败了~由于当前登陆帐号是老王本身,发送请求后服务器发现session所属用户wang2和account=liuxiaoer并非一我的,所以被拒绝。yii2

也就是说这个操做必须是 liuxiaoer 本身才能够,复仇的力量是可怕的,老王经过facebook层层找到了 liuxiaoer 就是快递员老刘的银行帐号。cookie

因而一个伟大的计划诞生了,老王的计划是这样的。

一、首先作一个网页,在网页中加入以下代码

src="https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2"

而后经过各类情景让老刘(liuxiaoer)访问了此网页。

二、当老刘(liuxiaoer)访问此网页后,上面的请求会被发送到银行,此刻还会带着老刘(liuxiaoer)本身的浏览器cookie信息,固然这样通常也不会成功,由于银行服务器发现老刘(liuxiaoer)并不在登陆状态。

三、老王想了一个招,他在淘宝找了一个灰色商人老李,让他经过种种方法,总之让老刘(liuxiaoer)经过浏览器给老李转了一次款。

四、就在第三步操做的2分钟内,老王成功让老刘(liuxiaoer)再一次访问了本身作的网页,你知道的,此刻老刘(liuxiaoer)在银行的session尚未过时,老王网页给银行服务器发送请求后,验证经过,打款成功。

五、老王收到了款,老刘(liuxiaoer)并不知道这一切,对于银行来讲这是一笔在正常不过的转帐。

这就是CSRF攻击,浏览器没法拦截。

CSRF攻击特色

基于上面血淋淋的故事,咱们总结下CSRF攻击的几个特色。

  • 黑客借助于受害者的cookie等浏览器信息骗取服务器新人,黑客并拿不到cookie等。
  • 因为浏览器同源策略,黑客没法拿到攻击的响应结果,能作的只是发起请求,你是否还记得不少钓鱼网站都模拟了登陆框么?
  • CSRF攻击主要是发送修改数据请求。

CSRF防护对象

所以咱们要保护的是全部能引发数据变化的客户端请求,好比新建、更新和删除。

CSRF防护方案

基于CSRF攻击特色,在业界目前防护 CSRF 攻击主要有三种策略:

  • 验证 HTTP Referer 字段;
  • 在请求地址中添加 token 并验证;
  • 在 HTTP 头中自定义属性并验证。

HEEP Referer

在http请求的时候,头部有一个叫作Referer的字段,该字段记录本次请求的来源地址。所以服务器端能够经过此字段是否为同一个域名来判断请求是否合法,由于客户本身作的网页发起的请求,其Referer为黑客网站。

这种方法最简单,而且不须要修改业务代码,咱们只须要对到达服务器的每一个请求作一次拦截分析便可。

可是此方法的缺点也是明显的,由于Referer的值是浏览器的,虽然HTTP协议不容许去修改,可是若是浏览器自身存在漏洞,那么就有可能致使Referer被人工设置,不安全。

好比IE6,就能够经过方法篡改Referer值。

就算是最新的浏览器此方法也不是绝对可用的,这涉及了用户的隐私,不少用户会设置浏览器不提供Referer,所以服务器在得不到Referer的状况下不能贸然的决绝服务,有可能这是一个合法请求。

添加Token

CSRF攻击之因此能成功,是由于黑客彻底伪造了一次用户的正常请求(这也是浏览器没法拦截的缘由),而且cookie信息就是用户本身的,那么咱们若是在请求中放入一些黑客没法去伪造的信息(不存在与cookie中),不就能够抵御了么!

好比在请求前生成一个token放到session中,当请求发生时,将token从session拿出来和请求提交过来的token进行对比,若是相等则认证经过,不然拒绝。token时刻在变化,黑客没法去伪造。

针对于不一样类型的请求通常方案是

  • GET 放到url中,好比http://url?csrftoken=xxxx
  • POST 放到表单的隐藏域 <input type=”hidden” name=”csrftoken” value=”xxxx”/>

对于GET请求,这里有一点要说明,在一个网站中请求的url不少,通常状况咱们是经过js对dom的全部节点进行遍历,发现a连接就在其href中增长token。

这里存在一个问题,好比黑客将本身网站的连接发到了要攻击页面,则黑客网站连接后面会有一个token,此刻客户能够经过编写本身网站代码获得这个token,而后用这个token马上构造表单,发起CSRF攻击,所以在js遍历的时候,若是发现不是本站的连接,能够不加token。

在HTTP头部增长属性

这个方法在思路上和上面的token方式同样,只不过将token放到了HTTP头部中,再也不参数传递,经过XMLHttpRequest类能够一次性的给全部请求加上csrftoken这个HTTP头属性并设置值。

这种方法适合上面批量添加token不方便的状况,一次性操做,不过局限性也比较大,XMLHttpRequest请求一般用在ajax方法中,并不是全部请求都适合。

Yii2

首先要说的是每种CSRF防范措施都有其弊端,不管你的防范多么严密,黑客拥有更多的攻击手段,所以在重要逻辑上(必须写入和删除)必须很是当心,接下来咱们把yii2框架在csrf上的部署说一下。

咱们以yii2.0.14为解说版本。

在CSRF这块,yii2框架采起了HTTP头部和参数token并行的方式,针对于每一个请求,在beforeAction都会作一次判断,以下

// vendor/yiisoft/yii2/web/Controller.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;
}

若是咱们没有设置 enableCsrfValidation 为false,而且没有报错,则会进行csrf验证,核心方法就是

Yii::$app->getRequest()->validateCsrfToken()

该方法存在于 vendor/yiisoft/yii2/web/Request.php 中,咱们看一看它。

public function validateCsrfToken($clientSuppliedToken = null) {
    // 省略上面代码
    return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
        || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
}

validateCsrfToken函数代码咱们只须要看最后的返回,getBodyParam或getCsrfTokenFromHeader方法获得的token,只要有一种验证经过,就认为合法。

以上是总体的思路,为了让你看的更清晰,我画一个图并增长一些名词解释。

图片描述

以上是yii2的csrf策略部署,固然我仍是推荐你使用 xdebug等调试工具 一步一步看看这个过程。

最后我在把上图的关键函数进行说明

  • generateCsrfToken() 该函数生成token并存到cookie或session中,该值不会随页面刷新而变化,它更多充当钥匙的做用,根绝它生成具体的csrfToken。
  • getCsrfToken() 生成具体的csrfToken,就是你在表单隐藏域中看到的那个值,这个值未来会传到服务器和真实的csrfToken进行对比,验证是否合法。
  • validateCsrfToken() 进行合法性验证,该函数获得一个真实的csrfToken而后和客户端上传来的csrfToken进行对比。

小结

原本想来个很高逼格的小结,想一想算了,华丽的词汇不以下一篇靠谱的干货实在,等我下一篇哈。

更多现代化 PHP 知识,请前往 Yii / PHP 学习社群