CSRF简介
CSRF全称是Cross Site Request Forgery,也就是跨站点请求伪造,是一种很常见的Web攻击。
在很多网站的操作中,往往使用者只需要一个Cookie和参数就可以对其内容的增添或者删除的操作,比如搜狐的博客就有:
http://blog.sohu.com/manage/entry.do?m=delete&id=156713012
这样当用户登陆的时候,就能通过访问这个连接达到删除博客的目的。这样假设我们诱导用户访问上面的地址,用户就会神不知鬼不觉的删除自己的文章。 这个删除博客文章的请求,就是攻击者所伪造的,所以这种攻击就被成为跨站点请求伪造。
CSRF进阶
浏览器Cookie策略
上面的例子中,攻击者伪造的请求之所以可以被服务器验证通过,是因为用户的浏览器成功的发送了Cookie的缘故。
浏览器所持有的Cookie分为两种:一种是Session Cookie,也就是临时Cookie;另一种是Third-party Cookie,也就是本地Cookie。
两者的区别再与Third-Party Cookie在Set-Cookie时候就指定了Expire的时间,只有到了Expire时间后Cookie才会是小,所以这种Cookie会保存在本地;而Session Cookie则没有指定Expire的时间,所以当浏览器关闭的时候,Cookie也失效了。
也就是说,Session Cookie在全浏览器的生命周期,即使浏览器打开了一个新的Tab页,Session Cookie也都是有效的。Session Cookie保存在浏览器的内存空间中;而Third-Party Cookie则保存在本地。
如果浏览器从一个域的页面中,要加载另一个域的资源,由于安全原因,某些浏览器会阻止Third-party Cookie的发送。
需要说明的是IE出于安全的考虑,默认禁止了浏览器在<img>
、<iframe>
、<script>
、<link>
等标签中发送第三方Cookie。而Firefox默认的策略是允许发送第三方cookie的。
目前主流的浏览器之中,默认会拦截Third-party Cookie的有:IE、Safari;不会拦截的则有:Firefox、Opera、Chrome、Android等。但是CSRF攻击的目标如果不需要使用cookie,那么也不必考虑浏览器的Cookie策略了。
P3P头的副作用
尽管有些CSRF攻击的实施起来不需要认证,不需要发送Cookie,但是不可否认的是,大部分敏感的操作都是躲在认证之后的。因此浏览器拦截第三方Cookie的发送,在某种程度上来说降低了CSRF攻击的威力。
但是P3P的介入却使得情况变得复杂。P3P是W3C指定的一项关于隐私的标准,全称是The Platform for 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。
如果www.b.com/test.html的内容为:
<iframe width=300 height=300 src="http://www.a.com/test.php"></iframe>
如果这http://www.a.com/test.php对a.com设置了Cookie的页面,其内容为:
如果请求www.b.com/test.html的时候,他的iframe会告诉浏览器去跨域请求www.a.com/test.php。test.php会尝试请求Set-Cookie,所以浏览器会收到一个Cookie。
如果设置Cookie成功之后,会再次请求该界面,浏览器会发送刚刚收到的Cookie,可是由于跨域限制,在a.com上Set-Cookie是不会成功的,所以无法发送刚才收到的Cookie,无论是临时还是本地的Cookie。
可是当加入P3P之后,其允许跨域访问数据,从而跨域Set-Cookie成功。
P3P头的介入会改变域名的隐私策略,从而使得<iframe>
、<script>
等标签在IE中不再拦截第三方Cookie的发送。P3P头只需要由网站设置一次即可,之后的每次请求都会遵循此策略,而不需要再重复设置。
P3P策略可以查询W3C标准。也可以直接引用一个XML策略文件。www.w3.org/TR/P3P
GET或者POST
不仅仅GET能够发起CSRF攻击,POST请求也能够发起CSRF攻击,所以不能仅凭请求方式来获取变量执行操作。
比如,在一个页面中构造好一个<form>
,然后使用Javascript自动提交这个表单,比如,攻击者在www.b.com/test.html中写入下面的代码。
攻击者甚至可以将这个页面隐藏于一个不可见的iframe的窗口中,这整个自动提交表单的过程来说,对于用户来说也是不可见的。
Flash CSRF
Flash也有许多的方式能够发起网络请求,包括POST。但是自IE 8起,Flash发送的网络请求已经不再发送本地Cookie了。
CSRF Worm
实现的原理可以举个例子:
假设现在有一个SMS服务,可以向指定的用户发送短消息:
http://msg.xx.com/?ct=22&cm=MailSender&tn=bmSubmit&sn=账号&co=消息内容
只需要修改参数sn,即可以对指定的用户发送短消息,而这里另一个接口则可以查询出某个用户的所有好友:
http://frd.xx.com/?ct=28&cm=FriList&tn=bmABCFriList&un=账号&callback=gotfriends
将两者结合起来,可以组成一个CSRF Worm,让一个用户查看恶意截面后,将给他的好友发送一条短消息,这个短消息又包含一张图片,其地址再次指向CSRF页面,使得这些好友再次将信息发给他的好友,这个worm因此得以传播。
首先:模拟服务器取得request的参数。
定义蠕虫页面的服务器地址,取得?和&符号后面的字符串,从URL中提取出感染蠕虫的用户名以及感染者好友的用户名。
其次:好友json数据的动态获取。
通过CSRF漏洞从远程加载受害者好友json数据,根据该接口的json数据格式,提取出好友数据为蠕虫的传播流程做准备。
最后:感染信息输出和消息发送的核心部分。
将感染者的用户和需要传播的好友用户名放到蠕虫链接内,然后输出短消息。
所以讲这两个结合起来就可以组成一个CSRF Worm,让一个用户查看恶意页面之后,将给他的所有好友发送一条短信息,这个短信息中又可以插入一张图片,其地址可以指向CSRF页面,使得这些好友再次把信息发送给他们的好友,这个worm就可以传播了。
这样可以把感染者的用户名和需要传播的好友用户名放到蠕虫连接内,然后输出短消息。这样的话,即使没有XSS漏洞,也能仅仅依靠CSRF,也是能够发送大规模蠕虫攻击的。
CSRF的防御
验证码
验证码被认为是对抗CSRF攻击最简单和有效的防御方法。
CSRF攻击的过程,往往是在用户不知情的情况下构造了网络请求。而验证码可以强制你用户必须与应用进行交互才能完成请求。因此在最终情况下,验证码能够很好的遏制CSRF攻击。
但是验证码并非是万能的,在很多时候,出于用户体验的考虑,网站不能给所有的操作都加上验证码。因此,验证码只能作为防御CSRF的一种辅助手段,而不能作为最主要的解决方案。
Referer Check
Referer Check在互联网之中最常见的应用就是防止图片。同样的,Referer Check也可以被用于检查请求是否来自合法的“源”。
常见的互联网应用,页面和页面之间都具有一定的逻辑关系,这样就使得每个正常请求的Referer具有一定的规律。
比如对于发帖这个操作,在正常的情况下是需要先登录到用户后台,或者访问有发帖功能的页面。再提交“发帖”的表单的时候,Referer的值必然是发帖表单所在的页面。如果Referer的值不是这个页面,甚至不是发帖网页的域,则极有可能是CSRF攻击。
即使我们能够通过检查Referer合法来判定用户是否被CSRF攻击,也仅仅是满足了防御的充分条件。Referer Check的缺陷再与,服务器并非什么时候都能取到Referer,很多用户出于隐私保护的考虑,限制了Referer的发送,在某些情况下,浏览器也不会发送Referer。比如从HTTPS跳转到HTTP的时候,出于安全的考虑,浏览器也不会发送Referer。
出于种种原因,我们还是无法依赖Referer Check作为防御CSRF的主要手段。但是通过Referer Check来监控CSRF攻击的发生,也是一种可行的方法。
CSRF_Token
现在业界对于CSRF的防御,一致的做法是使用一个Token。在介绍此方法之前,我们可以了解一下CSRF的本质。
CSRF的本质
CSRF能够攻击成功的原因,其本质是重要操作的所有参数都是可以被攻击者猜到的。攻击者只有预测到URL的所有参数和参数值,才能成功的构造出一个伪造的请求;反之,攻击者将无法攻击成功。
出于这个原因,我们可以把参数加密,或者使用一些随机数,从而可以让攻击者无法猜测到参数值:
比如,一个删除操作的URL是:http://host/path/delete?username=abc&item=123
其中就可以把username的参数改为hash值:http://host/path/delete?username=md5(salt+abc)&item=123
这样攻击者再不知道salt的情况下,是无法构造出这个URL的,因此也就无法发起CSRF攻击了。而对于服务器来说,则可以从Session或者Cookie中取得username=abc的值,再结合salt对于整个请求进行认证,正常请求会被认为是合法的。
但是这个方法也存在一些问题。首先加密或者混淆后的URL会变得很难读,对于用户很不友好。其次,如果加密后的参数每次改变,则某些URL将无法被用户收藏。最后,普通的参数如果也被加密或者hash,那么会给数据分析带来很大的困扰,因为数据分析工作常常需要用到参数的明文。
因此,我们可以使用anti-CSRF Token。
回到上面的URL中,保持原参数不变,新增一个参数Token。这个Token是随机,不可预测的:
http://host/path/delete?username=abc&item=123&token=[random(seed)]
Token需要爱足够随机,必须使用足够安全的随机数生成算法,或者采用真随机数生成器。Token应该被用户与服务器共同持有,不能被第三者知晓。在实际应用的时候,Token可以放在Session中,或者浏览器的Cookie中。
由于Token的存在,攻击者是无法构造出一个完整的URL实施CSRF攻击的。
Token需要同时放在表单和Session中,再提交请求的时候,服务器只需要验证表单中的Token,或者与用户Session(或者Cookie)中的Token是否一致。如果已知,则认为是合法请求,如果不一致或者有一个为空,则认为请求不合法,可能发生了CSRF攻击。通过可以把Token作为一个隐藏的input字段,放在form
中。
Token的使用原则
anti-CSRF在使用的过程中,需要注意:
防御CSRF的Token,是根据不可预测性原则设计的方案,所以Token的生成一定要随机,需要使用安全的随机数生成Token。
此外,这个Token目的不是为了防止重复提交。所以为了使用方便,可以允许在一个用户的有效生命周期内,在Token消耗前都使用同一个Token。但是如果用户已经提交了表单,则这个Token已经消耗掉,应该生成一个新的Token。
如果Token保存在Cookie中,而不是服务器端的Session之中,则会有个新问题。如果一个用户打开几个相同的页面同时操作,当某个页面的表单再次提交时,会出现Token错误。在这种情况下,可以考虑生成多个有效的Token,解决多页面共存的场景。
最后使用Token时候应该注意Token的保密性。Token如果出现在某个页面的URL之中,则可能通过Referer的方式泄露。
因此在使用Token的时候,应该尽量把Token放在表单之中,把敏感操作由GET改为POST,以form的形式提交,可以避免Token泄露。
同样的CSRF的Token仅仅用于对抗CSRF攻击,当网站还同时存在XSS漏洞时,这个方案就会变得无效,因为XSS可以模拟客户端浏览器执行任意操作,在XSS攻击下,攻击者完全可以请求页面,读出页面中的Token值,然后再构造出一个合法的请求。这个过程被称为XSRF和CSRF区分。
XSS带来的问题,应该使用XSS的防御方案综合解决,否则CSRF的Token防御就变得无效。