表面下,现代Web只有通过不断增长的技术标准才能实现。标准旨在管理技术和数据的互操作性。Web标准是最广泛采用和快速发展的标准之一,其变化也经常引起浏览器供应商,Web开发人员和用户之间的激烈争论。

在这篇博文中,我们将详细说明盲目遵从明确定义且普遍采用的Web标准所带来的危害。我们将对一个知名的数字货币服务发动远程攻击,并”窃取其中所有的货币“以此来证明我们观点的可靠性。

演示视频

Localhost Services(本地服务)

许多现代应用程序开始使用localhost “api-servers”作为将程序逻辑与用户界面分离的设计模式。这些服务会在127.0.0.1(localhost)上静静监听,并将应用程序的核心逻辑作为一个与平台无关的远程编程接口(RPC)进行无头封装。

C:\WINDOWS\system32>netstat -a -b Active Connections Proto Local Address Foreign Address State TCP 0.0.0.0:443 DESKTOP:0 LISTENING [vmware-hostd.exe] TCP 0.0.0.0:912 DESKTOP:0 LISTENING [vmware-authd.exe] TCP 0.0.0.0:5900 DESKTOP:0 LISTENING [siad.exe] TCP 0.0.0.0:49664 DESKTOP:0 LISTENING [Spotify.exe] TCP 0.0.0.0:57621 DESKTOP:0 LISTENING [Discord.exe] TCP 127.0.0.1:8307 DESKTOP:0 LISTENING [siad.exe] TCP 127.0.0.1:18171 DESKTOP:0 LISTENING [Battle.net.exe] TCP 127.0.0.1:27015 DESKTOP:0 LISTENING [AppleMobileDeviceProcess.exe] TCP 127.0.0.1:27060 DESKTOP:0 LISTENING [Steam.exe] TCP 127.0.0.1:52094 DESKTOP:0 LISTENING [NVIDIA Web Helper.exe]

在过去的几年里,对这些localhost API服务的研究已发现了许多可远程利用的问题。其中来自Google Project Zero的Tavis Ormandy的调查结果引人关注:

所有暴雪游戏(魔兽世界,守望先锋,暗黑破坏神III,星际争霸II等)都易受到DNS重绑定漏洞的攻击,允许任意网站运行任意代码。https://t.co/ssKyxfkuZo

— Tavis Ormandy (@taviso) 2018年1月22日

以下是一系列uTorrent DNS重绑定漏洞(现已修复),从远程代码执行到查询和复制下载文件等等。https://t.co/JEvhq1IHGJ

— Tavis Ormandy (@taviso) 2018年2月20日

最近的研究揭示了流行的视频会议应用程序Zoom中的一些可利用的问题

在加密货币领域,这种相同的“api-server”设计模式非常普遍。大量的区块链项目在他们的货币守护进程中使用这种架构。这些守护进程负责管理用户的加密钱包,执行事务以及与区块链保持同步。

1.png

通常,面向用户的GUI应用程序将连接到此本地服务,并将“high-level”概念(例如创建事务)转换为守护进程通过其公开的API提供的“low-level”区块链操作。此模型还允许高级用户或第三方开发人员轻松编写驱动,扩展或展示守护进程核心功能的代码。

Localhost 只是相对安全

将这些api-servers绑定且仅在127.0.0.1上运行,看上去似乎是一种安全且简单的方法来防止应用程序(例如货币/钱包守护进程)暴露于互联网和远程攻击。但遗憾的是,这并不总是一个安全的假设,特别是当与普通web浏览器共存时。

浏览网页时,你的浏览器会下载并运行大量“‘untrusted(不受信任)”的数据,以便在屏幕上为你呈现你喜爱的网站。通过扩展,在给定网站上发布的任何JavaScript都由本地计算机上的Web浏览器执行。这意味着远程发起和恶意编写的JavaScript可能会被用于在本地主机服务上进行探测。

将目光转向 Siacoin

让我们理论上的“预感”是,在浏览器内部执行的代码应该(原则上)能够与本地服务进行交互,并且只需运行它。在接下来的部分,我们将攻击Siacoin:一个知名的加密货币项目,旨在通过区块链技术提供廉价,高效和去中心化的文件存储。

我们的主要目标是成功执行对Sia/wallet/seed端点的API调用。在加密货币中,“wallet-seed(钱包种子)”是一个字符串,可用于重建与特定钱包相关联的私钥。如果你拥有了这个私钥,那就拥有资金。

我们可以通过创建一个恶意网站来测试这一理论,该网站试图从他们的本地守护进程中请求受害者的钱包种子:

2.png

但是我们的请求被阻止了!发生了什么?

显然,想通过浏览器攻击本地主机服务并不容易。这是因为现代Web浏览器采用了一种称之为”Same-Origin-Policy(SOP)“的保护策略。

SOP(同源策略)介绍

SOP最早是在Netscape Navigator 2(约1995年)中引入的,旨在规范对文档对象模型(DOM)的访问。随着网站越来越面向用户,JavaScript也越来越普遍,SOP明确了特定网页上的资源代码可以与之交互或修改的边界。

如果没有SOP,恶意网站可能会向其他网站发出请求,并从其响应中读取潜在的敏感信息。想象一下下面的恶意伪javaScript代码:

let req = await fetch("https://mail.google.com/") // Request data from current logged in gmail let mail_content = await req.text(); // Decode the response data exfil(mail_content); // Exfil the emails to an attacker

在没有SOP之前,恶意网站可能会执行这样的请求,以读取访问其网站的任何人的电子邮件!SOP的主要思想是,由于访问某个特定源站(如,attacker.com)而执行的脚本,不应与另一个源站(如,mail.google.com或localhost)上的数据进行交互。

为了强制执行此操作,浏览器会检查每个出站请求以确保其符合要求。当浏览器确定某个网站正在向其他来源发出请求时(“跨来源请求(cross origin request)”)时,它将首先检查该请求是否包含有任何“不安全”的标头。如果有,则浏览器将完全阻止该请求,如下所示:

3.png

相反,如果请求并未包含任何不安全的标头,则浏览器会将其转发到目标站点。这个“目标站点”现在可以选择告诉浏览器是否允许其他来源读取响应。此功能通过可由“目标站点”设置的跨域资源共享(CORS)标头实现。

通常,网站不启用CORS,或仅为特定域启用CORS。这意味着浏览器只会阻止传递响应。因此,请求站点无法读取响应数据。

4.png

如果某个特定请求被标记为“safe(安全)”,则允许其传递到目标站点。尽管这些请求被标记为“安全”,但对于给定的应用程序来说,这些请求仍然会带来很大的安全风险。以下面的请求为例:

fetch('http://localhost:1337/wallet/make_transaction', {
  method:'POST',
  body:'to=attack_address&amount=100000' })

以下是发送到服务器的实际数据:

POST /wallet/make_transaction Host: localhost:1337 Origin: http://attacker.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Accept: */*
Referer: http://attacker.com

to=attack_address&amount=100000

在本例中,恶意脚本控制的唯一数据块是路径和请求主体,这两部分在CORS下都被视为“安全的”。因此,尽管存在明显的实质性风险,但该请求总体上被认为是“安全的”!如果服务器不执行任何其他验证,则请求将成功触发事务。

既然我们已经知道了SOP是如何阻止我们的跨域请求的(http://localhost:9980/wallet/seed),那么接下来我们要做的就是想办法绕过SOP,让浏览器认为我们的恶意请求来自localhost(即同源)。我们可以通过一种称之为DNS重绑定的技术来做到这点。

DNS重绑定

DNS重绑定是一种现代技术,它会使浏览器误以为当前源站与实际不同的IP地址相关联。

这种类型的攻击可以通过控制特定的域名以及相关的DNS服务器来执行。当受害者访问域时,DNS服务器用真实的IP地址响应,但使用非常短的生存时间(TTL)来防止缓存。

之后,一旦TTL过期,就会向攻击者的域发出另一个请求(例如通过JavaScript)。然而,这次DNS服务器将以内部IP地址(如127.0.0.1)进行响应。浏览器则认为它仍在与原始的attacker.com通信,但现在请求将转到目标服务!

5.png

在此示例中,可以看到attacker.com第一次被解析为12.34.56.78,但第二次却被解析为了127.0.0.1。

现在,当attacker.com向自己发出请求时,浏览器会向127.0.0.1发出同源请求。

保护 Localhost API 服务器

针对这些攻击最强壮的防御是在向API发出请求时,需要一个在磁盘上的secret token:攻击者可能无法从远程上下文中知道这一点。然而,这有时并不理想,因为这会让API的使用变得更为困难,因此开发人员也经常为此寻找替代解决方案。

另一种常见的技术是验证请求头,以确保请求来自合法的客户端应用程序。执行此操作的常见方法是,检查主机头是否设置为localhost或其他预期值。另一种方法是检查浏览器要发送的某些头文件,如Origin、User-Agent或Referer。但是,这种“头检查”本身可能存在问题,因为哪些头可以被信任,哪些头可以被恶意脚本修改并不明确。

让我们来看一下siacoin守护进程是如何保护自己免受未经授权交互的……在项目生命初期,Sia的开发人员意识到来自浏览器的请求可能会成为一个问题。为了减轻这种风险,它们包含了以下代码,以确保守护进程只接受具有值为“Sia-Agent”的User-Agent的请求:

if !strings.Contains(req.UserAgent(), "Sia-Agent") {
    writeError(w, "Browser access disabled due to security vulnerability. Use Sia-UI or siac.", http.StatusBadRequest) return }

要绕过此检查,我们需要在执行跨域请求时指定User-Agent标头。让我们看看是否可行!

检查标准

要确定我们可以在出站请求中控制哪些标头,就需要我们对Web标准有更为深入的了解。这些标准定义了两个标头列表。第一个称为no-CORS-safe:它可以安全地为Cross-Origin请求设置标头(例如标头attacker.com可以发送到bank.com):

`Accept` `Accept-Language` `Content-Language` `Content-Type`

在执行跨域请求时,JavaScript可以设置这些标头,并且只能设置这些标头。如果设置了其他选项,浏览器将会阻止该请求。这就是为什么上面描述的用户代理过滤方法看起来是安全的原因。User-Agent不在白名单中,因此无法设置为跨域请求。

另一个列表是Forbidden列表:它明确禁止设置黑名单标头,无论其跨源状态如何(即使对于同一源请求,如bank.com发送到bank.com也不允许):

`Accept-Charset`                    | `Accept-Encoding`
`Access-Control-Request-Headers`    | `Access-Control-Request-Method`
`Connection`                        | `Content-Length`
`Cookie`                            | `Cookie2`
`Date`                              | `DNT`
`Expect`                            | `Host`
`Keep-Alive`                        | `Origin`
`Referer`                           | `TE`
`Trailer`                           | `Transfer-Encoding`
`Upgrade`                           | `Via`

有一些技巧可以阻止其中一些标头中被发送,但是它们无法被欺骗:如果它们存在,那么接收者可以信任它们。要注意,我们看到Origin和Referer列表,但User-Agent没有。这意味着对于同源请求,攻击者可以将User-Agent标头更改为他们想要的任意值!

让我们在Siacoin守护进程上测试一下吧!

完整的 Siacoin Exploit

我们把之前的那些片段都整合在一起:

Siacoin Daemon通过验证User-Agent标头来验证请求

允许Same-Origin请求设置自定义User-Agents,因为User-Agent不在Forbidden列表中

DNS重绑定允许我们将跨域请求转换为同源请求

要真正利用这个问题,我们需要针对http://localhost:9980设置DNS重绑定攻击。我们可以通过使用rbndr.us做到这一点,这是Tavis在他的相关研究中创建的一个实用程序。Rbndr提供了一个DNS服务器,可以在两个目标的IP之间进行切换,非常适用于这种攻击场景。

首先,我们在7f000001.<our_ip>.rbndr.us创建一个恶意站点,然后尝试访问/wallet/seeds。但是,我们仍然需要欺骗User-Agent标头。这是非常容易,你可以按如下方式进行操作:

我们只需要等待DNS记录的更新。一旦更新完成,我们将能够直接与Siacoin Daemon通信,那么用户的seed将举手可得。

这将导致许多严重的后果,最直接的就是如果钱包被“解锁(unlocked)”(假设用户正在运行Sia钱包应用程序的默认状态)那么我们就可以窃取受害者的钱包种子。这些种子可在之后用于不可撤销地转移受害者所有的资金。

从货币窃取到远程执行代码

通过这种攻击,我们不仅可以窃取受害者的资金,甚至还可以通过滥用Sia守护进程的预期功能来实现远程代码执行。

如前所述,Siacoin主要是一个去中心化的系统,用于促进廉价和可靠的文件存储。有权访问Sia钱包守护进程API的攻击者,可以使用它在去中心化存储网络和受害者计算机之间上传和下载任意文件。由于能够在目标计算机上以任意路径写入任意文件,我们能够以多种不同的方式演示代码执行。

受影响的浏览器

从我们的测试来看,谷歌的Chrome是唯一一款能够在DNS重新绑定攻击时,阻止设置用户代理字段的主流浏览器。

6.png

我们发现Apple Safari和Mozilla Firefox用户都很容易受到此帖中演示的攻击。 这是因为两个浏览器都正确遵循标准。 有趣的是,尽管Microsoft Edge浏览器也遵循该标准,但Microsoft Edge的用户却是安全的。这是由于Edge的app-container的内置网络隔离,使得浏览器无法实际建立与localhost的连接。

披露时间

在2018年9月我们向Nebulous Labs负责任的披露了该问题。他们对该漏洞的严重性表示了认同,并通过强化其使用磁盘令牌的服务及时解决了这个问题。

2018.09.12 – 向Nebulous Labs披露

2018.09.25 – 修复合并到source tree

2018.10.16 – 修复版发布(Sia 1.3.6)

*参考来源:ret2,FB小编secist编译,转自FreeBuf