## 4.1 CSRF简介  

什么是CSRF呢？我们先看一个例子。  

还记得在“跨站脚本攻击”一章中，介绍XSS Payload时的那个“删除搜狐博客”的例子吗？登录Sohu博客后，只需要请求这个URL，就能够把编号为“156713012”的博客文章删除。  

这个URL同时还存在CSRF漏洞。我们将尝试利用CSRF漏洞，删除编号为“156714243”的博客文章。这篇文章的标题是“testl”。  

搜狐博客个人管理界面  

攻击者首先在自己的域构造一个页面：  

其内容为：  

使用了一个<img>标签，其地址指向了删除博客文章的链接。攻击者诱使目标用户，也就是博客主“testltest”访问这个页面：  

执行CSRF攻击  

该用户看到了一张无法显示的图片，再回过头看看搜狐博客：  

文章被删除  

发现原来存在的标题为“testl”的博客文章，已经被删除了！  

原来刚才访问http://www.a.com/csrf.html时，图片标签向搜狐的服务器发送了一次GET请求：  

CSRF请求  

而这次请求，导致了搜狐博客上的一篇文章被删除。  

回顾整个攻击过程，攻击者仅仅诱使用户访问了一个页面，就以该用户身份在第三方站点里执行了一次操作。试想：如果这张图片是展示在某个论坛、某个博客，甚至搜狐的一些用户空间中，会产生什么效果呢？只需要经过精心的设计，就能够起到更大的破坏作用。  

这个删除博客文章的请求，是攻击者所伪造的，所以这种攻击就叫做“跨站点请求伪造”。  

## 4.2 CSRF进阶  

## 4.2.1 浏览器的Cookie策略  

在上节提到的例子里，攻击者伪造的请求之所以能够被搜狐服务器验证通过，是因为用户的浏览器成功发送了Cookie的缘故。  

浏览器所持有的Cookie分为两种：一种是“Session Cookie”，又称“临时Cookie”;另一种是“Third-party Cookie”，也称为“本地Cookie”。  

两者的区别在于，Third-party Cookie是服务器在Set-Cookie时指定了Expire时间，只有到了Expire时间后Cookie才会失效，所以这种Cookie会保存在本地；而Session Cookie则没有指定Expire时间，所以浏览器关闭后，Session Cookie就失效了。  

在浏览网站的过程中，若是一个网站设置了Session Cookie，那么在浏览器进程的生命周. 期内，即使浏览器新打开了Tab页，SessionCookie也都是有效的。Session Cookie保存在浏览器进程的内存空间中；而Third-party Cookie则保存在本地。  

如果浏览器从一个域的页面中，要加载另一个域的资源，由于安全原因，某些浏览器会阻止Third-party Cookie的发送。  

下面这个例子，演示了这一过程。  

在http://www.a.com/cookie.php中，会给浏览器写入两个Cookie:个为Session Cookie，另一个为Third-party Cookie。  

访问这个页面，发现浏览器同时接收了这两个Cookie。  

浏览器接收Cookie  

这时再打开一个新的浏览器Tab页，访问同一个域中的不同页面。因为新Tab页在同一个浏览器进程中，因此Session Cookie将被发送。  

Session Cookie被发送  

此时在另外一个域中，有一个页面http://www.b.com/csrf-test.html，此页面构造了CSRF以访问www.a.com。  

这时却会发现，只能发送出Session Cookie，而Third-party Cookie被禁止了。  

只发送了Session Cookie  

这是因为IE出于安全考虑，默认禁止了浏览器在<img>、<iframe>、<script>、<link>等标签中发送第三方Cookie。  

再回过头来看看Firefox的行为。在Firefox中，默认策略是允许发送第三方Cookie的。  

在Firefox中允许发送第三方Cookie  

由此可见，在本章一开始所举的CSRF攻击案例中，因为用户的浏览器是Firefox，所以能够成功发送用于认证的Third-party Cookie，最终导致CSRF攻击成功。  

而对于IE浏览器，攻击者则需要精心构造攻击环境，比如诱使用户在当前浏览器中先访问目标站点，使得Session Cookie有效，再实施CSRF攻击。  

在当前的主流浏览器中，默认会拦截Third-party Cookie的有：IE6、IE 7、IE 8、Safari; 不会拦截的有：Firefox 2、Firefox 3、Opera、Google Chrome、Android等。  

但若CSRF攻击的目标并不需要使用Cookie，则也不必顾虑浏览器  

的Cookie策略了。  

## 4.2.2 P3P头的副作用  

尽管有些CSRF攻击实施起来不需要认证，不需要发送Cookie，但是不可否认的是，大部分敏感或重要的操作是躲藏在认证之后的。因此浏览器拦截第三方Cookie的发送，在某种程度上来说降低了CSRF攻击的威力。可是这一情况在“P3P头”介入后变得复杂起来。  

P3P Header是W3C制定的一项关于隐私的标准，全称是The Platformfor Privacy Preferences。  

如果网站返回给浏览器的HTTP头中包含有P3P头，则在某种程度上来说，将允许浏览器发送第三方Cookie。在IE下即使是<iframe>、<script>等标签也将不再拦截第三方Cookie的发送。  

在网站的业务中，P3P头主要用于类似广告等需要跨域访问的页面。但是很遗憾的是，P3P头设置后，对于Cookie的影响将扩大到整个域中的所有页面，因为Cookie是以域和path为单位的，这并不符合“最小权限”原则。  

假设有www.a.com与www.b.com两个域，在www.b.com上有一个页面，其中包含一个指向www.a.com的iframe。  

http://www.b.com/test.html的内容为：  

http://www.a.com/test.php是一个对a.com域设置Cookie的页面，其内容为：  

当请求http://www.b.com/test.html时，它的iframe会告诉浏览器去跨域请求www.a.com/test.php。test.php会尝试Set-Cookie，所以浏览器会收到一个Cookie。  

如果Set-Cookie成功，再次请求该页面，浏览器应该会发送刚才收到的Cookie。可是由于跨域限制，在a.com上Set-Cookie是不会成功的，所以无法发送刚才收到的Cookie。这里无论是临时Cookie还是本地Cookie都一样。  

测试环境请求过程  

可以看到，第二次发包，只是再次接收到了Cookie，上次Set-Cookie的值并不曾发送，说明没有Set-Cookie成功。但是这种情况在加入了P3P头后会有所改变，P3P头允许跨域访问隐私数据，从而可以跨域Set-Cookie成功。  

修改www.a.com/test.php如下：  

再次重复上面的测试过程：  

测试环境请求过程  

可以看到，第二个包成功发送出之前收到的Cookie。  

P3P头的介入改变了a.com的隐私策略，从而使得<iframe>、<script>等标签在IE中不再拦截第三方Cookie的发送。P3P头只需要由网站设置一次即可，之后每次请求都会遵循此策略，而不需要再重复设置。  

P3P的策略看起来似乎很难懂，但其实语法很简单，都是一一对应的关系，可以查询W3C标准。比如：  

CP是Compact Policy的简写；CURa中CUR是<current/>的简写；a是always的简写。如下表：  

此外，P3P头也可以直接引用一个XML策略文件：  

若想了解更多的关于P3P头的信息，可以参考W3C标准 [1] 。  

正因为P3P头目前在网站的应用中被广泛应用，因此在CSRF的防御中不能依赖于浏览器对第三方Cookie的拦截策略，不能心存侥幸。  

很多时候，如果测试CSRF时发现<iframe>等标签在IE中居然能发送Cookie，而又找不到原因，那么很可能就是因为P3P头在作怪。  

## 4.2.3 GET? POST?  

在CSRF攻击流行之初，曾经有一种错误的观点，认为CSRF攻击只能由GET请求发起。因此很多开发者都认为只要把重要的操作改成只允许POST请求，就能防止CSRF攻击。  

这种错误的观点形成的原因主要在于，大多数CSRF攻击发起时，使用的HTML标签都是<img>、<iframe>、<script>等带“src”属性的标签，这类标签只能够发起一次GET请求，而不能发起POST请求。而对于很多网站的应用来说，一些重要操作并未严格地区分GET与POST，攻击者可以使用GET来请求表单的提交地址。比如在PHP中，如果使用的是\$_REQUEST，而非\$_POST获取变量，则会存在这个问题。  

对于一个表单来说，用户往往也就可以使用GET方式提交参数。比如以下表单：  

用户可以尝试构造一个GET请求：来提交，若服务器端未对请求方法进行限制，则这个请求会通过。  

如果服务器端已经区分了GET与POST，那么攻击者有什么方法呢？对于攻击者来说，有若干种方法可以构造出一个POST请求。  

最简单的方法，就是在一个页面中构造好一个form表单，然后使用JavaScript自动提交这个表单。比如，攻击者在www.b.com/test.html中编写如下代码：  

攻击者甚至可以将这个页面隐藏在一个不可见的iframe窗口中，那么整个自动提交表单的过程，对于用户来说也是不可见的。  

在2007年的Gmail CSRF漏洞攻击过程中，安全研究者pdp展示了这一技巧。  

首先，用户需要登录Gmail账户，以便让浏览器获得Gmail的临时Cookie。  

用户登录Gmail  

然后，攻击者诱使用户访问一个恶意页面。  

攻击者诱使用户访问恶意页面  

在这个恶意页面中，隐藏了一个iframe，iframe的地址指向pdp写的CSRF构造页面。  

这个链接的实际作用就是把参数生成一个POST的表单，并自动提交。  

由于浏览器中己经存在Gmail的临时Cookie，所以用户在iframe中对Gmail发起的这次请求会成功——邮箱的Filter中会新创建一条规则，将所有带附件的邮件都转发到攻击者的邮箱中。  

恶意站点通过CSRF在用户的Gmail中建立一条规则  

Google在不久后即修补了这个漏洞。  

## 4.2.4 Flash CSRF  

Flash也有多种方式能够发起网络请求，包括POST。比如下面这段代码：  

除了URLRequest外，在Flash中还可以使用getURL，loadVars等方式发起请求。比如：  

在IE 6、IE 7中，Flash发送的网络请求均可以带上本地Cookie;但是从IE 8起，Flash发起的网络请求已经不再发送本地Cookie了。  

## 4.2.5 CSRF Worm  

2008年9月，国内的安全组织80sec公布了一个百度的CSRF Worm。  

漏洞出现在百度用户中心的发送短消息功能中：  

只需要修改参数sn，即可对指定的用户发送短消息。而百度的另外一个接口则能查询出某个用户的所有好友：  

将两者结合起来，可以组成一个CSRF Worm——让一个百度用户查看恶意页面后，将给他的所有好友发送一条短消息，然后这条短消息中又包含一张图片，其地址再次指向CSRF页面，使得这些好友再次将消息发给他们的好友，这个Worm因此得以传播。  

Step 1 ：模拟服务器端取得request的参数。  

定义蠕虫页面服务器地址，取得?和&符号后的字符串，从URL中提取感染蠕虫的用户名和感染者的好友用户名。  

Step 2：好友json数据的动态获取。  

通过CSRF漏洞从远程加载受害者的好友json数据，根据该接口的json数据格式，提取好友数据为蠕虫的传播流程做准备。  

Step 3：感染信息输出和消息发送的核心部分。  

将感染者的用户名和需要传播的好友用户名放到蠕虫链接内，然后输出短消息。  

这个蠕虫很好地展示了CSRF的破坏性——即使没有XSS漏洞，仅仅依靠CSRF，也是能够发起大规模蠕虫攻击的。  

## 4.3 CSRF的防御  

CSRF攻击是一种比较奇特的攻击，下面看看有什么方法可以防御这种攻击。  

## 4.3.1 验证码  

验证码被认为是对抗CSRF攻击最简洁而有效的防御方法。  

CSRF攻击的过程，往往是在用户不知情的情况下构造了网络请求。而验证码，则强制用户必须与应用进行交互，才能完成最终请求。因此在通常情况下，验证码能够很好地遏制CSRF攻击。  

但是验证码并非万能。很多时候，出于用户体验考虑，网站不能给所有的操作都加上验证码。因此，验证码只能作为防御CSRF的一种辅助手段，而不能作为最主要的解决方案。  
