一、引言
最近在鼓捣跨站脚本包含(XSSI)攻击时,我突然意识到可以通过HTTP状态码来玩泄漏信息和跨域。如果你想到的是“XSSI登录神谕”,那么说明你的已经上道了,但该攻击还可以扩展到更多的情形中。这里说的登录神谕通常是根据当前认证状态来决定加载与否的一些JavaScript文件。然而,这种攻击还可以针对HTML、JSON、XML或任意的内容类型。这实际上就是为XSSI攻击开辟了一个新的战场:从GET参数枚举信息,一次一位。
对于这种类型的攻击手法,我至今尚未见过公开的介绍,所以我将尽力使这篇文章尽可能全面。这意味着这篇文章会很长,所以我将其分为几部分,具体如下所示:
1. 攻击手法
2. 该攻击的必要条件
3. 防御措施
4. 进一步探讨
5. 小结
为了节约篇幅,本文不会解释XSSI的基础知识,这方面的内容,可以参考另一篇文章,地址为https://www.scip.ch/en/?labs.20160414。我认为这是有关这个主题的最佳参考文章。在本文中,我提出了一种注入非脚本内容的新型攻击手法。针对非脚本内容的更强攻击手法,上面提到的那篇文章也有相关的解释,但是与下面将要演示的方法相比,要想实施那些攻击,它们要求的条件会更加苛刻(编码和注入技巧)。
二、新型攻击手法
该攻击手法的基本思想与XSSI登陆神谕非常类似。攻击者会尝试将脚本标签加载到指向不同来源的页面。通过onerror、onload和window.onerror函数,攻击者可以弄清楚跨源服务器如何响应GET请求的信息的。让我感到吃惊的是,在收到一个非2XX的响应时,会执行onerror函数,否则就会执行onload函数。除非强制实施了严格的内容类型限制,否则不管返回的内容类型如何(请参阅下文必要条件1),都是如此。
它的意义何在呢?我们能从200 vs 400响应中了解到什么? 嗯,这取决于端点,以及其他多种可能因素。毕竟,HTTP状态码旨在返回信息,并且经常用于返回API的信息。
1.几个简单的例子
现在让我们假设,如果你通过了身份认证,那么/admin目录就会返回一个200状态码和HTML页面,否则,就会返回一个401状态码和HTML错误页面。那么,这不仅可以作为一个登录神谕,同时还允许进行权限的枚举。如果每个用户都有唯一的个人信息页面(即:/profile/dennis),则恶意站点可以使用类似的攻击来识别特定用户,以便进行进一步攻击,并且不会引起安全响应团队的警觉。如果一个页面具有基于GET请求的SQL注入漏洞,但攻击者却无法访问该页面的话,那么,攻击者可以诱骗经过身份验证的用户访问处于攻击者控制之下的页面,从而间接发动注入攻击,然后将结果跨域泄漏给攻击者的JavaScript代码。
2.一个更有趣的例子
下面,我们将深入考察一个更有趣的例子。假设这里有一个票务系统,它提供了查找客户信息的搜索字段。如果发送GET到“/search?c=d*”的话,其中“*”字符用作通配符,将返回以字母“d”开头的所有客户信息和200状态码。如果没有客户匹配“d *”模式,则返回500状态码。如果攻击者想要获得这些用户信息,但他们无法登录,那怎么办?他可以设法让已经登录的用户代为发送请求,并告诉onload函数“是的,我找到了某人”,或告诉onerror函数“搜索没有返回任何结果”。
这类似于SQL盲注,不同之处在于这里需要借助第三方,同时,这里滥用的是同源策略而非语法。注意,在这里不需要关心票据系统在正文中返回的内容类型。搜索可以返回JSON、XML、HTML甚至一幅图像,只要没有返回nosniff头部,对这个攻击就没有影响。同时,URL参数可以包含在脚本src属性中,这样攻击者就可以创建如下所示的脚本了:
- d = document.createElement('script');
- d.src = victim_domain + "/search?c=a*";
这将发送一个GET请求到票务系统上的API“/search?c=a*”。这里,攻击者只是设置了onload和onerror事件来分别记录成功和失败情况:
- d.onload = function(){client_exists("a*")};
- d.onerror = function(){client_does_not_exist("a*")};
然后将它附加到DOM对象上面:
- document.head.appendChild(d);
如此一来,攻击者网站的所有访问者都会自动向票务系统跨域发送GET请求。如果有一个以“a”开头的客户,那么端点将返回200,并且onload函数将会被执行。攻击者的onload处理程序会将另一个脚本加载到DOM中,查询是否有以“aa”开头的客户。如果出现了onerror事件,那说明没有以字母“a”开头的客户,所以攻击者会将另一个脚本加载到DOM中,检查是否有以字母“b”开头的客户。该脚本将一直使用树搜索算法进行搜索,直到返回有效的客户名称为止。
一旦找到了一个客户名称,攻击者就可以使用相同类型的攻击来搜索需要客户名称的其他API端点,并返回其他的信息, 例如,搜索与客户相关联的电子邮件地址的端点。此外,攻击者还可以搜索匹配“*”模式的客户。如果此操作失败,则表示访问者没有访问票务系统的客户搜索的相应权限,所以也就无需发送其他请求了。由于窃取信息的请求是由攻击者站点的访问者发出的,所以攻击可以让所有访问者并行发送请求。如果将这些攻击手法与社会工程电子邮件结合一起,甚至可以从内部引发票务系统信息泄漏事件。
所以,这种攻击不是很牵强,也不需要特殊的条件。
三、攻击的必要条件
简单来说,需要具备以下条件:
1. 不返回'X-Content-Type-Options:nosniff'HTTP头部,除非内容类型是JavaScript。
2. 端点必须响应GET请求。
3. 端点的状态码:200类型响应表示成功,非200类型响应表示失败。
4. 该信息不可公开获取。
最重要的是,这里除了第一个条件中的JavaScript之外,根本没有提及内容类型。也就是说,这种攻击适用于XML、JSON、图像或任何其他内容。有关该攻击的必要条件的更多详细信息,请参阅防御措施部分。对于渗透测试人员来说,也应该阅读这一节,因为它对某些技巧进行了更加深入的解释。
四、防御措施
要想防御这种攻击,你只需要设法让上面的任意一个必要条件达不到要求就行了。下面,让我们从防御角度来深入讨论这些必要条件。
必要条件1
如果网站返回“X-Content-Type-Options:nosniff”HTTP头部的话,那么该攻击就会偃旗息鼓了。所以,最简单的防御措施,就是让它返回这个头部。服务器可以通过nosniff头部告诉浏览器,“当我说将给你时,就意味着这是真的!”。
为什么这个方法能奏效?这是因为,任意类型的文件都可以通过HTTP提供,但是,Web开发人员却并不总是正确地声明相应的文件类型。因此,当浏览器请求JavaScript文件时,内容类型头部可能会说它实际上是HTML。因此,浏览器就会抛出错误消息,直到它尝试将文件解析为JavaScript为止。此时,由于onload函数已经执行,所以任何解析错误都将调用window.onerror函数。如果内容类型未正确声明,nosniff头部的存在就意味着onerror将立即被调用。总是调用onerror就意味着没有发现差异和没有信息损失。如果内容类型是JavaScript,那么nosniff就没有帮助,所以你照样可以发起XSSI攻击。
注意:这只适用于支持nosniff头部的浏览器。IE和Chrome是第一批支持此头部的浏览器。Firefox也声明将要提供相应的支持,具体时间我还不清楚,但我发现Firefox 50已经支持nosniff头部,但是Firefox 45.5仍然没有提供相应的支持。我假设Edge跟IE一样,但我没有亲自测试它们。
必要条件2
脚本标签只能用于GET请求。因此,如果端点只接受POST请求,则此攻击就无能为力了。这个要求看似简单,但务必要小心。您可能已经设计了只接收POST请求的API,但您的内容管理系统也许仍然可以接收GET请求.。
必要条件3
如果端点始终返回200,那么也就无法从状态码中窃取信息了。但是,状态码的存在,是自有其存在的理由的! 不要仅仅为了阻止这种攻击而简单粗暴地废弃了HTTP协议的核心部分。请改用nosniff头部来阻止该攻击。
虽然固定的HTTP状态码可以阻止这里描述的这种攻击,但是却无法防御其他攻击。例如,顶层的JSON数组就可以解析为JavaScript,尽管顶层的JSON对象无法解析为JavaScript。因此,即使您的端点始终返回200状态码,照样可以创建window.onerror函数,然后根据是否存在解析错误来收集信息。只要把Content-Type头部设置为JSON,利用nosniff头就能阻止这种攻击。
必要条件4
如果攻击者能够在自己的浏览器中加载私密信息,那么他就不需要这种攻击了。该攻击主要是设法让用户访问攻击者的域,然后以用户在其他域的权限来获取更多信息,这通常要求用户已经经过了相应的认证。除此之外,如果您的家庭路由器有此漏洞,那么恶意公共站点可以通过它请求脚本,从而导致信息泄漏。
五、进一步探讨
3XX状态码
虽然本文没太关注打开重定向和3XX响应,但是它们可能进一步扩展该攻击。到目前为止,重定向到2XX的行为好像跟2XX类似,而重定向到非2XX的行为与非2XX的行为类似。这意味着如果发现打开的重定向,通过检查referer头保护自己的端点,如果启用了重定向的话,就可能会被绕过。这也是一个很好的思路。
其他标签
我相信,指向跨域的img标签的行为跟script标签的行为类似。也许在img和script标签中加载资源可能会由于解析差异而导致更多的信息泄露。此外,CSS也值得进一步仔细研究。
其他属性
我希望Subresource Integrity会导致进一步的信息泄漏,但它却明智地要求应用CORS了。如果你可以绕过CORS,那么这种攻击将会大放异彩。
我花了大量时间来测试通过onload,onerror和window.onerror获取信息的方法,其实,我们还可以考察其他诸多的属性,因为这可能会发现其他攻击方法,或通过每个请求获取更多的信息。
六、结束语
在加载跨域资源的时候,任何可检测的差异都能提供宝贵的信息。这些信息可能与登录神谕一样微不足道,但也可能与证书一样造成严重的信息泄露(虽然不太可能)。
防御方:内容类型的分歧是各种攻击的常见手段。使用HTTP头部nosniff严格限制内容类型能够减轻这种攻击和其他攻击。这样做的另外一个好处是, 对不当的响应将产生显眼的错误提示,便于人们进行相应的修复工作。
攻击方:人们对于同源策略的理解还不是非常深入,这使它成为bug的一个重要来源。一定要注意查找GET请求中返回的敏感信息,然后看看通过script标签跨域请求信息时,是否可以检测到任何行为的差异。
作者:shan66
来源:51CTO