4.1 CSRF 概述
CSRF是跨站请求伪造,可能刚接触CSRF这个概念的人会很容易把它与XSS混淆。我们知道,攻击的发生是由各种请求造成的,对于CSRF来说,它的请求有两个关键点:跨站点的请求与请求是伪造的。
4.1.1 跨站点的请求
从字面上看,跨站点请求的来源是其他站点,比如,目标网站的删除文章功能接收到来自恶意网站客户端(JavaScript、Flash、HTML等)发出的删除文章请求,这个请求就是跨站点的请求,目标网站应该区分请求来源。
字面上的定义总是狭义的,这样恶意的请求也有可能来自本站。
4.1.2 请求是伪造的
伪造的定义很模糊,一般情况下,我们可以认为:如果请求的发出不是用户的意愿,那么这个请求就是伪造的。在第3章“前端黑客之XSS”中我们知道,对于XSS来说,发起的任何攻击请求实际上都是目标网站同域内发出的,此时已经没有同源策略的限制,虽然这样,我们同样可以认为这些请求也是伪造的,因为它们不是用户的意愿。
4.1.3 一个场景
对于CSRF来说,强调这两个关键点是想表达CSRF的安全风险在大多数场景中的共同点,而在大多数场景中,这种攻击是XSS无法完成的。
我们先来看这个“大多数场景”是什么。
目标网站A:www.a.com
恶意网站B:www.b.com
两个域不一样,目标网站A上有一个删除文章的功能,通常是用户单击“删除链接”时才会删除指定的文章,这个链接是www.a.com/blog/del?id=1,id号代表不同的文章。
我们知道,这样删除文章实际上就是发出一个GET请求,那么如果目标网站A上存在一个XSS漏洞,执行的JS脚本无同源策略限制,就可以按下面的方式来删除文章。
· 使用AJAX发出GET请求,请求值是id=1,请求目标地址是www.a.com/blog/del。
· 或者动态创建一个标签对象(如img、iframe、script)等,将它们的src指向这个链接www.a.com/blog/del?id=1,发出的也是GET请求。
· 然后欺骗用户访问存在XSS脚本的漏洞页面(在目标网站A上),则攻击发生。
如果不用这种方式,或者目标网站A根本不存在XSS漏洞,还可以如何删除文章?看看CSRF的思路,步骤如下:
· 在恶意网站B上编写一个CSRF页面(www.b.com/csrf.htm),想想有什么办法可以发出一个GET请求到目标网站A上?
· 利用AJAX?不行,它禁止跨域传输数据。
· 那么,用代码<img src=http://www.a.com/blog/del?id=1/>。
· 然后欺骗已经登录目标网站A的用户访问www.b.com/csrf.htm页面,则攻击发生。
这个攻击过程有三个关键点:跨域发出了一个GET请求、可以无JavaScript参与、请求是身份认证后的。
1)跨域发出了一个GET请求
在第1章中,我们提到Web层面上有一个非常重要的策略(即同源策略),这个策略用来限制客户端脚本的跨域请求行为,但实际上由客户端HTML标签等发出的跨域GET请求被认为是合法的,不在同源策略的限制中,但是这些请求发出后并没能力得到目标页面响应的数据内容。
很多网站其实都需要有这样的功能,比如,嵌入第三方资源:图片、JS脚本、CSS样式、框架内容,尤其是很多开放的Web 2.0网站有个mashup应用聚合概念,如Google的Gadgets或者SNS社区中的第三方Web应用与Web游戏,通过iframe嵌入第三方扩展应用,如果将这样的GET请求限制住,那么Web世界就过于封闭了。
安全风险总是出现在正常的流程中,现在我们发出的是一个删除文章的GET请求,对于合法的跨域请求,浏览器会放行。
2)可以无JavaScript参与
大家看到了,CSRF这个过程与XSS不一样,不需要JavaScript参与,当然也可以有JavaScript参与,比如在www.b.com/csrf.htm中使用JavaScript动态生成一个img对象:
<script> new Image().src = 'http://www.a.com/blog/del?id=1' </script>
同样可以达到攻击效果。需要特别注意的是:这里并不是JavaScript跨域操作目标网站A的数据,而是间接生成了img对象,由img对象发起一个合法的跨域GET请求而已,这个过程和上面直接用一个img标签一样。
3)请求是身份认证后的
这一点非常关键,跨域发出的请求类似这样:
GET /blog/del?id=1 HTTP/1.1 Host: www.a.com User-Agent:Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0 Connection: keep-alive Referer: http://www.b.com/csrf.htm Cookie: sid=0951abe6d508dab60357804519a61b999;JSESSIONID=abcTePo2Ori_k-pW t5net;
而如果是目标网站A,用户自己单击删除链接时发出的请求类似这样:
GET /blog/del?id=1 HTTP/1.1 Host: www.a.com User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0 Connection: keep-alive Referer: http://www.a.com/blog/ Cookie:sid=0951abe6d508dab60357804519a61b999;JSESSIONID=abcTePo2Ori_k-pW t5net;
可以看到两个请求中,除了请求来源Referer值不一样外,其他都一样,尤其是这里的Cookie值,该Cookie是用户登录目标网站A后的身份认证标志。跨域发出的请求也同样会带上目标网站A的用户Cookie,这样的请求就是身份认证后的,攻击才会成功。
看上去这个过程很容易理解,其实我们漏了一个非常关键的概念:在第2章详细提过的,Cookie分本地Cookie与内存Cookie,这两类Cookie在CSRF的过程中会存在一些差异,IE浏览器默认不允许目标网站A的本地Cookie在这样的跨域请求中带上,除非在HTTP响应头中设置了P3P(Plat-form for Privacy Preferences),这个响应头告诉浏览器允许网站(恶意网站B)跨域请求目标网站A的资源时带上目标网站A的用户本地Cookie。对于非IE浏览器,就没这样的限制。
好了,通过这个场景,我们已经知道了CSRF的过程,这个过程中只介绍了GET请求的情况,那么POST请求呢?比如,目标网站A的“写文章”功能,这是一个提交表单的操作,会发起POST请求。同样,这个POST请求可以从恶意网站B中发出,通过JavaScript自动生成一份表单,表单的action地址指向目标网站A的“写文章”表单提交地址,表单的相关字段都准备好后,即可发出请求。下面看一段代码:
<body></body> <script> function new_form(){ // 创建表单函数 var f = document.createElement("form"); document.body.appendChild(f); f.method = "post"; return f; } function create_elements(eForm, eName, eValue){ // 创建表单项函数,eForm: 表单对象,eName: 表单项,eValue: 表单项值 var e = document.createElement("input"); eForm.appendChild(e); e.type = 'text'; e.name = eName; if(!document.all){ e.style.display = 'none'; } else{ e.style.display = 'block'; e.style.width = '0px'; e.style.height = '0px'; } // 兼容浏览器的隐藏设置,目的是让表单不可见 e.value = eValue; return e; } var _f = new_form(); // 创建表单对象 create_elements(_f, "title", "hi"); // 创建表单项:title=hi create_elements(_f, "content", "csrf_here"); // 创建表单项: content=csrf_here _f.action= "http://www.a.com/blog/add"; // 设置表单action提交地址为目标网站A的/blog/add页面 _f.submit(); // 自动提交 </script>
构造完成,当目标网站A的用户被欺骗访问了恶意网站B的该页面时,一个跨域的伪造的POST表单请求就发出了。同样,这个请求带上了目标网站A的用户Cookie。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论