7.4 发布者漏洞
虽然大多数时间你都在处理由于跨站脚本和跨站请求伪造引起的安全漏洞,但还有一些其他漏洞。攻击者利用这些漏洞可以直接入侵你的Web应用。我们也希望能够覆盖到这些方面,它们应该归纳在Web应用安全的主题内容中单独介绍。
本章的剩余部分,我们将把重点放在涉及发布者的漏洞上。与传统的Web应用不同,第三方应用涉及多方,其中之一的发布者也并不能完全信任。因为你的应用代码(至少一部分)是在他们的页面上执行的,他们可以有意或者无意地对你的应用进行攻击。在本节中,将简要介绍最常见的几种漏洞:发布者模拟、点击劫持和拒绝服务。
7.4.1 发布者模拟
发布者模拟指的是恶意的发布者使用另外一个发布者的名义注册你的应用。这样一来,攻击者就可以获取被攻击发布者的展示数据,而且攻击者所执行的操作都将错误的指向被攻击者。
我们来看一个例子。如果你还记得第1章,就应该对发布者的脚本引入片段还有印象,发布者需要在他们的Web页面中嵌入脚本片段以加载你的应用。假设已经让发布者在使用应用之前事先注册,而且提供给他们的引入脚本(如程序清单7.6所示)中需要通过配置变量指定发布者的账户名(或者其他可识别令牌)。
程序清单7.6 标识发布者的脚本引入片段
如果其他发布者失误或者出于恶意将这段代码安装到了他们的网站上会怎样呢?有些初级的发布者,可能不会阅读你的文档,而是直接从另外一个网站复制粘贴代码就开始安装,因此其中的配置参数并不是针对自己网站的。这种情况下,Camera Stork服务会误以为在当前页面执行的操作都来自原发布者。
对于Camera Stork微件而言,现在这种情况并没有那么严重。可能发生的最坏情况是顶替页面对原发布者造成影响。但是对于其他的第三方应用而言,这种结果可能更糟。例如,如果顶替页面加载了一个针对其他发布者的统计脚本(类似Google Analytics),可能会对原发布者的数据造成破坏,从而对业务产生重创。另外一个可能的情况是:如果发布者在使用你的收费服务,根据带来的服务器负载进行计费,顶替页面可能会对其他发布者造成影响,产生额外的费用。根据我们的经验来看,发布者肯定不喜欢这样的事情发生。
通过REFERER头对发布者进行验证
对发布者进行最强的防御措施就是使用Referer HTTP头对请求部分进行校验。你可能对它并不熟悉,浏览器在每个HTTP请求中都会发送Referer头,并且包含请求来源的文档URL。也就是说,通过验证该文档是否是属于发布者的有效URL即可。如果Referer头包含的URL是一个未知的域名,就可以拒绝该请求并返回一个错误响应。
要这么做之前,需要发布者提交一组可信任的域名,这可以通过在你的网站上维护一个表格来实现(如图7.3所示)。你需要将这些值存入数据库,并将它们关联并指向发布者的账号。
然后可以在服务端代码中执行信任域名的验证。假设初始的脚本文件(widget.js)是通过服务端应用提供的。当该资源被请求时,你需要查找发布者的受信任域名并验证Referer中的URL是否在这些值的范围内。程序清单7.7是一个用Python实现的简单的Flask服务端接口,演示了如何执行该验证。
程序清单7.7 使用Referer HTTP头信息对发布者的域名进行验证
该例子中只检查了初始的脚本文件,但是最好对第三方应用发起的所有动态HTTP资源请求(如AJAX调用)都执行检查。此外,请注意该例子并不包括在iframe内的页面发起的资源请求,因为这些请求都来自你的域名而非发布者。
Referrer检查并非完美的解决方案。Referrer头是由浏览器提交的,因此可以通过其他工具进行欺骗,如浏览器扩展或者网络代理。然而,通过检查referrer值可以消除最明显的发布者欺骗,更不用说是失误了。
7.4.2 点击劫持
点击劫持,也被称为界面伪装攻击,是一种在网页中将恶意代码等隐藏在看似无害的内容(如按钮)之下,并诱使用户点击的手段。这样使访问者在不知不觉的情况下执行其他网站的操作,其中包括已经验证的会话信息。
让我们看一个例子。假设有一个摄影器材公司CheapCo,通过Camera Stork在线商店销售自己的产品。该设备公司非常渴望在你的网站上能够有更好的评论,他们原本可以通过改善自己的产品来实现,但他们选择了欺骗。
要实现这一点,CheapCo在他们的主页上通过一个隐藏的iframe加载Camera Stork网站。当有人访问他们的主页时,他们通过JavaScript将iframe定位在访问者的鼠标光标下方。当用户单击CheapCo网站上一个看起来正常的UI元素时,实际上单击的是iframe中的Web页面。CheapCo可以将iframe内容定位在一个特定的UI元素上,比如定位在对自己产品提交五星评论的按钮上。
1.X-FRAME-OPTIONS和“框架杀手”脚本
防止点击劫持最直接的办法就是避免Web页面通过iframe加载,有多种方式都可以实现。第一种是在内容请求的响应中返回一个特殊的HTTP头X-Frame-Options。X-Frame-Options可以指定这两个值中的任意一个:sameorigin,只允许同源的iframe展示该内容;deny,阻止任何iframe展示该内容。
下面是一个使用X-Frame-Options的HTTP响应的示例。
X-Frame-Options之所以以X开头,是因为该头信息并非HTTP规范的一部分。它是2009年微软针对伪装攻击在IE8中推出的,并一直被其他浏览器厂商沿用。因为该HTTP头最近才被采用,因此旧的浏览器并不支持。
幸运的是,还有另外一种方式可以防止页面通过iframe加载:使用被称为“框架杀手”的JavaScript代码片段。这些代码使用JavaScript检测当前文档是否在框架内展示,如果是的话便会阻止页面加载。可以通过执行无限循环、隐藏内容,或者将文档重定向到一个空白页面来阻止目标页面的渲染。
并非所有的框架杀手脚本都一样,有些很容易受到框架杀手克星的影响:父页面加载的脚本使框架杀手失效(我们提供的例子恰好不受影响)。
下面是一个框架杀手的例子,如果不是顶层窗口则隐藏文档正文。
2.点击劫持和第三方脚本
对于第一方Web应用而言,这些解决方案足够了,但是对于第三方应用加载iframe的情况呢?在这样的情况下,使用X-Frame-Options头或者框架杀手脚本都不可行,因为你需要在发布者的页面上加载这些内容。可以采取一些替代措施来减少点击劫持。
所有由单击触发的数据修改操作都需要确认。如果iframe中的操作可能会对数据进行修改,需要用户通过某种无法进行点击劫持的方式进行操作的确认。最常见的做法是在新窗口中打开一个确认对话框。另外,也可以使用在输入框里输入验证码的方式。
需要发布者进行注册。如果需要发布者注册后才能安装你的应用,即便发布者滥用也会有据可查,从而可以封掉这些投机取巧的发布者。
点击劫持很可怕,因为它是Web和HTML的一个漏洞,并不是应用或者软件的缺陷。最糟糕的情况就是所有网站都受到点击劫持的攻击,除非他们采取措施进行避免。对于一个不是通过iframe加载的普通内容而言,使用我们先前提到的X-Frame-Options头或者框架杀手脚本可以很容易避免。对于第三方应用而言,解决方案没有那么直接,但依旧是可控的。
7.4.3 拒绝服务
拒绝服务(DoS)攻击是试图让一个Web应用无法被用户访问。这种攻击最常见的方式就是让目标服务器的HTTP请求数达到饱和,从而不再响应正常的请求,因此导致Web应用无法正常访问。DoS攻击是令人崩溃的攻击形式中的一种,如果服务器遭到DoS攻击的话,目前还没有针对它们的通用解决方案。
拒绝服务攻击会影响所有的Web应用程序,但是第三方应用的危险更大。因为第三方应用可以通过其他Web页面访问,任何一个页面的流量激增都可能导致你的服务出现问题。甚至不需要向发布者发起攻击,需要做的只是像Justin Bieber的粉丝一样向你的微件发送超大量级的流量。
针对这种攻击的常见防御措施都围绕网络硬件,但是不在本书的讨论范围内。我们有一个建议:如果你的服务需要发布者进行注册,考虑为每个发布者创建一个唯一的子域名来提供文件(例如,publisher.camerastork.com)。这样一来,如果某个发布者对应用造成了大量的请求,你便可以封禁或者限制该发布者的请求。这有助于你的服务对于DoS攻击和随机流量的控制。