跨站点请求伪造(CSRF)

CSRF的全名是Cross Site Request Forgery,翻译成中文就是跨站点请求伪造。

 

1)CSRF 简介

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

在介绍XSS Payload时那个“删除搜狐博客”的例子,登录Sohu博客后,只需请求这个URL,就能把编号为“156713012”的博客删除。

http://blog.sohu.com/manage/entry.do?m=delete&id=156713012

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

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

http://www.a.com/csrf.html

其内容为:

<img src="http://blog.sohu.com/manage/entry.do?m=delete&id=156714243" />

使用了一个 <img> 标签,其地址指向了删除博客文章的链接。

攻击者诱使目标用户,也就是博客主“test1test” 访问这个页面:

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

发现原来存在的标题为“test1”的博客文章,已经被删除了!

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

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

回顾整个攻击过程,攻击者仅仅诱使用户访问了一个页面,就以该用户身份在第三方站点里执行了一次操作。这个删除博客文章的请求,是攻击者所伪造的,所以这种攻击就叫做“跨站点请求伪造”。

 

2)GET?POST?

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

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

<form action="/register" id="register" method="post" >
<input type=text name="username" value="" />
<input type=password name="password" value="" />
<input type=submit name="submit" value="submit" />
</form>

用户可以尝试构造一个GET请求:

http://host/register?username=test&password=passwd

来提交,若服务器端未对请求方法进行限制,则这个请求会通过。

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

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

<form action="http://www.a.com/register" id="register" method="post" >
<input type=text name="username" value="" />
<input type=password name="password" value="" />
<input type=submit name="submit" value="submit" />
</form>
<script>
var f = document.getElementById("register");
f.inputs[0].value = "test";
f.inputs[1].value = "passwd";
f.submit();
</script>

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

 

3)CSRF 防御

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

1. 验证码

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

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

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

 

2. 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攻击的发生,倒是一种可行的方法。

 

3. Anti CSRF Token

现在业界对CSRF的防御,一致的做法是使用一个Token。在介绍此方法前,先了解一下CSRF的本质。

 

CSRF的本质

CSRF为什么能够攻击成功?其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。攻击者只能预测出URL的所有参数与参数值,才能成功构造一个伪造的请求;反之,攻击者将无法攻击成功。出于这个原因,可以想到一个解决方案:把参数加密,或者使用一些随机数,从而让攻击者无法猜测到参数值。这是”不可预测性原则“的一种应用。

比如,一个删除操作的URL是:

http://host/path/delete?username=abc&item=123

把其中的username参数改成哈希值:

http://host/path/delete?username=md5(salt+abc)&item=123

这样,在攻击者不知道salt的情况下,是无法构造出这个URL的,因此也就无从发起CSRF攻击了。而对于服务器来说,则可以从Session或Cookie中取得”username=abc“的值,再结合salt对真个请求进行验证,正常请求会被认为是合法的。

但是这个方法也存在一些问题。首先,加密或混淆后的URL将变得非常难读,对用户非常不友好。其次,如果加密的参数每次都改变,则某些URL将无法再被用户收藏。最后,普通的参数如果也被加密或哈希,将会给数据分析工作带来很大的困扰,因为数据分析工作常常需要用到参数的明文。

因此,我们需要一个更加通用的解决方案来帮助解决这个问题。这个方案就是使用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中:

同时Cookie中也包含了一个Token:

 

Token的使用原则

Anti CSRF Token在使用时,有若干注意事项。

防御CSRF的Token,是根据”不可预测性原则“ 设计的方案,所以Token的生成一定要足够随机,需要使用安全的随机数生成器生成Token。

此外,这个Token的目的不是为了防止重复提交。所以为了使用方便,可以允许在一个用户的有效生命周期内,在Token消耗掉前都使用同一个Token。但是如果用户已经提交了表单,则这个Token已经消耗掉,应该再次重新生成一个新的Token。

如果Token保存在Cookie中,而不是服务器端的Session中,则会带来一个新的问题。如果一个用户打开几个相同的页面同时操作,当某个页面消耗掉Token后,其他页面的表单 内保存的还是被消耗掉的那个Token,因此其他页面的表单再次提交时,会出现Token错误。在这种情况下,可以考虑生成多个有效的Token,以解决多页面共存的场景。

最后,使用Token时应该注意Token的保密性。Token如果出现在某个页面的URL中,则可能会通过Referer的方式泄露。比如以下页面:

http://host/path/manage?username=abc&token=[random]

这个manage页面是一个用户面板,用户需要在这个页面提交表单或者单击”删除“按钮,才能完成删除操作。

在这种场景下,如果这个页面包含了一张攻击者能指定地址的图片:

<img src="http://evil.com/notexist" />

则”http://host/path/manage?username=abc&token=[random]" 会作为HTTP请求的Referer 发送到eveil.com的服务器上,从而导致Token泄露。

因此在使用Token时,应该尽量把Token放在表单中。把敏感操作由GET改为POST,以form表单(或者AJAX)的形式提交,可以避免Token泄露。

此外,还有一些其他的途径可能导致Token泄露。比如XSS漏洞或者一些跨域漏洞,都可能让攻击者窃取到Token的值。

CSRF的Token仅仅用于对抗CSRF攻击,当网站还同时存在XSS漏洞时,这个方案就会变得无效,因为XSS可以模拟客户端浏览器执行任意操作。在XSS攻击下,攻击者完全可以请求页面后,读出页面内容里的Token值,然后再构造出一个合法的请求。这个过程可以称之为XSRF,和CSRF以示区分。

XSS带来的问题,应该使用XSS的防御方案予以解决,否则CSRF的Token防御就是空中楼阁。安全防御的体系是相辅相成、缺一不可的。

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值