7.3 跨站请求伪造
在第4章中,我们谈到了同源策略。该策略被所有浏览器支持,并防止不同来源的Web页面互相访问对方的方法和属性。你已经学到了一些技巧,使用这些技巧可以向你的服务器跨域发送消息。在本节中,我们将介绍利用该特性是如何转而对你进行攻击的。
跨站请求伪造(CSRF或者XSRF)是针对用户的另一种类型的漏洞。与XSS不同,XSS是将伪造的内容展现给用户,而XSRF利用的是Web应用程序与用户浏览器之间的信任。在本质上,易受XSRF攻击的Web应用都允许攻击者通过自己的网站诱导用户执行某些操作。
在本节中,我们首先来看一些典型的XSRF攻击的例子,然后介绍这一漏洞的变种:JSON劫持。最后,我们讨论防御XSRF攻击的策略。
7.3.1 XSRF攻击
设想如下场景:你为发布者提供了一个添加内容版主的Web接口,内容版主可以编辑、删除通过他们网站中的微件提交的评论。该接口需要身份认证。在将其添加到现有版主列表之前,需要检查调用接口的用户是否通过身份验证并有操作的权限。对于这个例子而言,假设需要添加一个新的版主,用户需要提交(POST方式)一个含有用户名的表单。
服务端处理该表单请求的代码,如下所示。
从各方面来看,该服务端接口看起来似乎很安全。毕竟它对请求用户的会话和权限级别都做了验证,而且对POST参数中的用户名也做了验证。但很可惜,尽管我们做了这么多努力,这个接口还是很容易受到XSRF的攻击。要理解其中的原因,只需要看程序清单7.4,这是攻击者提供的恶意表单,会利用访问者的身份向moderators/add接口发送POST请求。
程序清单7.4 攻击者利用XSRF漏洞提交自己的用户名作为内容版主
尽管这看起来像一个普通的Web页面,但网页上的脚本会自动生成并提交一个指向moderators/add接口的表单。换言之,只要用户访问这个页面,就会将攻击者的用户名作为参数向moderators/add提交一个POST请求。在浏览器看来,这就是向camerastork.com服务器发起的一个正常请求,因此会在请求中传输存储在camerastork.com域下的cookies,包括用户的会话cookie。服务端接收到包含cookie的请求后会检查会话并验证提供的用户名。一切看起来都很正常,因此服务端会将攻击者作为内容版主添加到你的账户中。攻击就这样悄无声息的成功完成了。
7.3.2 JSON劫持
刚刚你已经了解了XSRF漏洞的经典例子,但不幸的是,XSRF攻击有多种不同形式的变种。其中之一就是所谓的JSON劫持,常用来窃取用户的受保护信息。如果你还记得第4章中我们提到的JSONP,就应该还记得< script >标签并不受同源策略的限制,而且你曾利用这一点在微件中发送跨域HTTP请求。JSON劫持正是利用了SOP的这种例外情况,通过标准的< script >标签加载该接口,从而窃取JSON数据。
假如说Camera Stork微件允许用户对过去的购买记录进行评论,这种信息就被认为是隐私,用户并不希望将他们的购买历史公开给其他用户。微件中为了获取这部分数据,需要在服务端实现一个接口,通过JSON格式返回当前会话用户的购买历史。你就需要在一个Camera Stork域名下的iframe页面中使用XMIHttpRequest调用该接口获取数据。下面是接口响应的示例。
假设攻击者想要使用XSRF漏洞来获取这些数据。正如你所知道的,如果攻击者可以利用用户(访问恶意Web页面的用户)的身份发送该接口请求,服务端便会读取该用户的会话并返回相应的购买记录。攻击者只需要能够访问响应的正文就大获全胜了。但是具体怎么做呢?同源策略会阻止使用XMLHttpRequest的方式进行请求,因为接口与攻击者的页面隶属不同的域。只剩下使用< script >标签的方式来请求接口了,但攻击者会遇到另外一个问题:他们无法捕获响应数据的正文,因为JSON输出并没有赋值给某个变量或者持久化。因此,看起来购买历史接口的安全性似乎是有充分保障的。
事实上并不一定。对于一些旧的浏览器,可以通过自定义Object原型的setter方法来捕获传递给它的任何值。也就使得捕获接口的JSON输出成为可能,比如捕获购买历史接口返回的输出结果,见程序清单7.5。
程序清单7.5 JSON劫持一个看起来安全的HTTP接口
有了页面上的这段代码,攻击者只需要将不知情的访问者引诱到他们的网站,并使用< script >标签加载有漏洞的JSON接口。攻击者便可以将捕获到的变量内容发送到他们的服务器。这并不是一个假设的情况,这种方式曾经被用来提取一个访问者的Twitter追随者。
好消息是,在如今的一系列现代浏览器中该漏洞已经不复存在。然而,它并非浏览器JSON劫持的唯一切入点,可能也不是最后一个。
7.3.3 保护应用免受XSRF攻击
保护应用免受XSRF攻击的标准方式称之为XSRF令牌。一个XSRF令牌是一个不可预知的值,通常是一个同当前用户会话相关,但无法重用的随机生成的字符串。执行受保护操作的每个页面都包含这样的令牌。当用户提交一个操作时,表单或者JavaScript代码会将XSRF令牌作为请求的一部分一并提交,同时服务端会校验令牌的合法性。如果令牌丢失或者无效,整个请求会被拒绝。XSRF令牌应当与单个会话相关联,否则攻击者就可以使用通过他们自己会话生成的令牌。
图7.2展示了XSRF令牌发起和校验的过程。
从本质上讲,当发出一个XSRF令牌时,就相当于为用户提供了一个对应操作的一次性唯一许可,而该操作通过独立的HTTP请求是禁止访问的。并且用户只有在访问包含对应操作的页面时才能获得该许可。因为攻击者无法访问该令牌,他们也就不能诱导用户发起这些需要令牌的资源请求。
XSRF令牌的有效性只取决于服务端同浏览器用户之间的交换。如果在发布者的页面代码中生成一个XSRF令牌,恶意页面就可以获取该令牌并像之前一样进行攻击。解决方案是:只在外部iframe(通过camerastork.com访问)的页面中提供XSRF令牌,这样父文档便无法访问到。这也就意味着,如果要使用该令牌,向你的服务器发起的任何请求都需要在iframe内进行(使用window.postMessage或者easyXDM)。
在JSON劫持的情况下,除了标准的XSRF保护方式之外,还可以在JSON响应的第一行添加一些有问题的JavaScript代码,这样攻击者就不可能通过< script >标签成功访问。例如,你可以在服务器JSON响应的第一行添加一个简单的无限循环,如下。
如果攻击者通过< script >标签加载数据就无法绕过第一行,因为浏览器在执行该循环时会暂停响应。另一方面,你的微件可以通过XMLHttpRequest以纯文本的方式获取这些数据,然后丢弃掉第一行,再将响应转换为一个JavaScript对象即可。
大多数的Web开发框架(如Ruby on Rails和Python的Django)都有针对XSS和XSRF的内置策略。如果你正在使用某个Web框架,一定要先了解它的文档并熟悉这些工具。
本节介绍了一个可恶的漏洞:跨站请求伪造,作为一个第三方JavaScript开发者,这是一个要时刻考虑的问题。虽然我们所描述的防御机制并不复杂,但应该足以防御大多数XSRF漏洞。XSS和XSRF漏洞是Web应用中成功攻击的主要方式,对于第三方JavaScript应用更是如此,所以我们才花费时间来讨论它们。