作者:Eyal Itkin
原文链接:https://research.checkpoint.com/2020/apache-guacamole-rce/
翻译:知道创宇404实验室

概述

在许多公司,日常工作包括每天到办公室在公司计算机上安全地使用公司内网。有时,员工可能需要特殊的异地访问,并使用工具远程连接到公司的网络。

然而,自从 COVID-19 大流行爆发以来,这种日常工作已经发生了逆转。在 Check Point,与世界各地许多其他的公司一样,现在绝大多数工作都是远程完成的。这种从现场工作到异地工作的转变意味着现在比以往任何时候都更多地使用用于远程连接到公司网络的 IT 解决方案。这也意味着这些解决方案中的任何安全漏洞都会产生更大的影响,因为公司依靠这项技术来保持其业务正常运转。

Apache Guacamole是一种流行的远程工作基础架构,在全球的docker 下载量超过1000 万次。在我们的研究中,我们发现 Apache Guacamole 容易受到几个关键的反向 RDP 漏洞的攻击,并且还受到FreeRDP 中发现的一些新漏洞的影响。简而言之,这些漏洞允许已经成功入侵组织内部计算机的攻击者在毫无戒心的工作人员尝试连接到受感染的计算机时对 Guacamole 网关发起攻击。然后黑客可以完全控制服务器,并拦截和控制所有其他连接的会话。

在这个简短的视频演示中,我们展示了如何设法利用这些漏洞并成功控制 Guacamole 网关和所有连接的会话:

https://youtu.be/vm8i9jJ2Np0

介绍 Apache Guacamole

我们决定将远程工作的技术解决方案作为一个有趣的研究课题。事实上,我们的 IT 部门立即恳请我们审查一个这样的解决方案:Apache Guacamole。如前所述,它是市场上较为突出的工具之一。不仅许多组织使用该产品连接到他们的网络,许多网络和安全产品也在他们自己的产品中嵌入了 Apache Guacamole。包括:Jumpserver Fortress、Quali、Fortigate等等。

经过一番摸索,我们画出了推荐的网络架构的基本草图,如图1所示:

guacamole_figure_1

图 1:部署 Apache Guacamole 网关的典型网络架构。

从本质上讲,员工使用浏览器连接到公司面向 Internet 的服务器,通过身份验证过程,然后访问他的公司计算机。当员工只使用他的浏览器时,guacamole服务器会选择一种支持的协议(RDP、VNC、SSH 等)并使用开源客户端连接到特定的公司计算机。连接后,guacamole服务器充当中间人,在将事件从所选协议转换为特殊的“guacamole协议”时来回中继事件,反之亦然。

现在我们了解了架构,还有一些有希望的攻击向量需要检查:

1、反向攻击场景:企业网络内的一台受感染机器利用传入的良性连接攻击网关,旨在接管它。

2、恶意员工场景:一名恶意员工使用网络内的计算机来利用他对连接两端的控制并控制网关。

我们需要一个 0-Day 吗?

在我们深入研究代码之前,让我们简要地关注一下 FreeRDP。在我们之前对反向 RDP 攻击的研究中,我们发现了这个 RDP 客户端中的几个关键漏洞,使其暴露在恶意 RDP“服务器”的攻击之下。换句话说,恶意公司计算机可以控制连接到它的毫无防备的 FreeRDP 客户端。我们甚至为我们的一个漏洞 ( CVE-2018-8786 ) 提供了一个基本的 PoC,并且我们已经演示了远程代码执行。

通过查看Apache Guacamole已发布的版本,我们可以看到只有 2020 年 1 月底发布的 1.1.0 版本增加了对最新 FreeRDP 版本(2.0.0)的支持。知道我们在 FreeRDP 中的漏洞仅在 2.0.0-rc4 版本上进行了修补,这意味着2020 年 1 月之前发布的所有版本都使用易受攻击的 FreeRDP 版本。

我们本可以在这里停下来,估计大多数公司尚未升级到最新版本的可能性很高,并且可能已经使用这些已知的 1-Days 进行了攻击。但是,我们决定再次搜索 RDP 协议中的漏洞,更具体地说:

1、guacamole-server的代码,同时只关注对RDP协议的支持。

2、最新发布的 FreeRDP 版本代码:2.0.0-rc4 版。

最重要的是,我们的利用条件是它能够在默认安装上工作,仅使用默认启用的功能,并且希望不需要来自客户端的任何交互。开始吧。

寻找新的漏洞

熟悉 FreeRDP 的代码,以及整个 RDP,在这次安全审计中真的很有帮助。我们很快就开始寻找漏洞。

CPR-ID-2141 – 我们的第一个信息泄露漏洞

CVE: CVE-2020-9497

文件: protocols\rdp\channels\rdpsnd\rdpsnd-messages.c

功能: guac_rdpsnd_formats_handler()

旁注:由于 Apache 没有使用我们报告的漏洞 (CPR-ID) 和他们发布的 CVE-ID 之间的 1:1 映射,我们将主要参考漏洞通过他们的(更准确的)CPR-ID

为了在 RDP 连接和客户端之间中继消息,开发人员为默认 RDP 通道实现了他们自己的扩展。一个这样的通道负责来自服务器的音频,因此毫不奇怪地称为rdpsnd(RDP 声音)。

然而,通常情况下,guacamole-serverFreeRDP 之间的集成点被证明是容易出错的。传入的消息由 FreeRDP 的wStream对象包装,并且应使用此对象的 API 解析数据。但是,如图 2 所示,开发人员忘记强制传入流对象必须包含与数据包声明的字节数匹配的字节数。

guacamole_figure_2

图 2:缺少输入过滤导致越界读取。

通过发送恶意的rdpsnd通道消息,恶意的 RDP 服务器可能会导致客户端认为该数据包包含大量字节,这些字节实际上是客户端本身的内存字节。这反过来会导致客户端用这些字节向服务器发回响应,并给 RDP 服务器一个大量的、心脏出血式的信息泄露原语。

CPR-ID-2142 – 再次信息泄露

CVE: CVE-2020-9497

文件: protocols\rdp\channels\rdpsnd\rdpsnd.c

功能: guac_rdpsnd_process_receive()

在同一个 RDP 通道中,不同的消息具有类似的漏洞。这次它将越界数据发送到连接的客户端,而不是返回到 RDP 服务器。

guacamole_figure_3

图 3:类似的越界读取,这次是将数据泄露给客户端。

虽然有用,但这种泄露会将信息发送给客户端,我们希望在客户端甚至不知道网关受到攻击的情况下构建漏洞。

CPR-ID-2143 – 仍然是,信息泄露

CVE: CVE-2020-9497

文件: protocols\rdp\plugins\guacai\guacai-messages.c

功能: guac_rdp_ai_read_format()

我们很想找到一个额外的频道guacai,负责声音信息。该通道负责“音频输入”,因此得名guacai。尽管容易受到与前一个频道大致相同的漏洞的影响,但默认情况下该频道是禁用的。

图 4:另一个越界读取,就像第一个一样。

在我们研究的这一点上,我们发现了 3 个主要的信息泄露漏洞,这对于绕过 ASLR(地址空间布局随机化)应该绰绰有余。然而,我们仍然需要一个内存损坏漏洞来完成我们的漏洞利用链。感觉卡住了,我们又去看看 FreeRDP,希望能找到我们之前研究中可能遗漏的漏洞。

FreeRDP,我们的老朋友

自从我们上次查看 RDP 客户端以来,没有对其进行太多更改;补丁版本仍然是迄今为止发布的最新版本。在寻找漏洞时总是如此,让我们首先了解wStream该客户端使用的类型中的一个关键设计“功能” 。在图 5 中,我们可以看到这个结构体的字段:

guacamole_figure_5

图5:该wStream对象,用来包裹传入/传出分组。

这是一个简单的流包装器的经典示例:

buffer – 指向接收包开始的指针。

pointer – 指向接收数据包内的读取头的指针。

length – 传入数据包的大小,以字节为单位。

在从输入流解析给定字段之前,应进行检查以确保流足够大以容纳它。这样的检查可以在图 6 中看到:

guacamole_figure_6

图 6:检查可用输入,使用Stream_GetRemainingLength().

我们再怎么强调这个输入检查的重要性都不为过。每次解析或跳过字段时,指针字段都会相应地前进。稍后,当执行下一次检查时,它看起来像这样:

guacamole_figure_7

图 7:使用当前流的头部计算剩余长度。

一旦指针字段通过传入数据包的末尾,此计算将下溢,从而返回一个巨大的无符号值,该值应表示剩余的负字节数。总之,漏了一个检查,剩下的就没用了。正如我们很快发现的那样,这种有趣的设计选择使 FreeRDP极易受到越界读取漏洞的影响。

在我们介绍这些越界读取漏洞之前,重要的是要注意我们为什么关心它们。通常,读取只有在以某种方式将读取的字节返回给攻击者时才有用。否则,读取只能用作在尝试访问内存中未映射的页面时使程序崩溃的一种方式。Apache Guacamole 攻击场景很特别,因为我们拥有连接的两端。例如,如果内存字节被解析为屏幕的图形更新,这些更新仍将发送到连接的客户端。

在这种攻击场景中,每个越界读取漏洞都可能变成一个弱但仍然有用的信息披露。

CPR-ID-2145 和 CPR-ID-2146 – FreeRDP 中的越界读取

记住wStream对象中有趣的设计缺陷,我们所要做的就是寻找不受检查支持的读取操作。这很有效,我们发现了两个这样的漏洞:CPR-ID-2145CPR-ID-2146

但是,在向供应商报告它们时,我们发现它们都已经被两个不同的小组报告了。由于这些是重复的,即使我们仅在几个小时后提交了它们,也应该将它们归于合法的研究人员。

因此,我们决定让其他团队展示他们的发现更为合适,并从我们的博客文章中删除了有关他们的详细信息。

我们需要一个内存损坏......

此时,我们发现了 5 个漏洞,这些漏洞可以作为我们攻击中的信息披露利用原语。但是,我们甚至还没有发现一个内存损坏漏洞。在 FreeRDP 中寻找此类漏洞非常烦人,因为每次我们有线索时,检查都会阻止它。很多时候,这个检查是针对我们报告的漏洞的补丁,所以我们真的不能抱怨太多。

Zensploitation Twitter(@zensploitation)中的这篇帖子几乎总结了我们在研究中此时的感受:

guacamole_figure_8

图 8:https://twitter.com/zensploitation/status/1244598246879547393

在这一点上,我们认为我们已经走得太远了,不能简单地放弃。我们决定再看一遍guacamole服务器,这一次我们收获颇丰。

CPR-ID-2144 – 最后,内存损坏

CVE: CVE-2020-9498

文件: protocols\rdp\plugins\guac-common-svc\guac-common-svc.c

功能: guac_rdp_common_svc_handle_open_event()

RDP 协议将不同的“设备”公开为单独的“通道”,每个设备一个。这些包括rdpsnd声音通道、cliprdr剪贴板通道等等。作为一个抽象层,通道消息支持分片,允许它们的消息长达 4GB。为了正确支持rdpsnd和rdpdr(设备重定向)通道,guacamole-server 的开发人员添加了一个额外的抽象层,在文件中实现:guac_common_svc.c.图 9 显示了在此文件中实现的碎片处理:

guacamole_figure_9

图 9:处理传入的通道片段。

我们可以看到第一个片段必须包含该CHANNEL_FLAG_FIRST片段,并且在处理时根据总消息的总声明长度分配一个流。

但是,如果攻击者发送没有此标志的片段会发生什么?它似乎只是简单地附加到以前的剩余流中。在这一点上,这看起来是一个很有前途的 Dangling-Pointer 漏洞。现在我们只需要检查开发人员是否记得将其设置NULL为前一个碎片消息完成处理时。

guacamole_figure_10

图 10:在不清除悬空指针的情况下释放使用的流。

图 10 清楚地表明,在碎片消息完成重组并继续解析后,它被释放。就是这样。没有人将悬空指针设置为NULL!

恶意 RDP 服务器可能会发送使用先前释放的wStream对象的乱序消息片段,从而有效地成为一个 Use-After-Free 漏洞。最重要的是,这wStream是我们希望为此类漏洞获得的最强大的对象,因为如果将指针字段设置为所需的内存地址,它可以用于任意写入。最重要的是,rdpsnd在我们损坏的wStream对象被使用后,我们在通道中有一个有用的信息泄露漏洞。通过一些努力,一个特制的wStream对象可以将我们的原始漏洞变成一个更强大的任意读取漏洞利用原语。

最后,远程代码执行 (RCE)

如前所述,通过使用漏洞 CVE-2020-9497CVE-2020-9498,我们设法实现了我们的任意读取和任意写入漏洞利用原语。使用这两个强大的原语,我们成功实现了远程代码执行漏洞利用,其中guacd当远程用户请求连接到他的(受感染)计算机时,恶意公司计算机(我们的 RDP“服务器”)可以控制进程。

guac_pe_fig_1

图 11:漏洞利用截图——从接管guacd过程中弹出一个计算。

但并没有就此结束。该guacd进程仅处理单个连接并以低权限运行。传统上,此时我们需要一个权限提升漏洞来接管整个网关。事实上,在与 Apache 协调披露期间,维护人员提出的问题之一是这种攻击场景是否真的可能发生。我们能否以某种方式仅从一个guacd进程接管网关中的所有连接?

让我们来了解一下。

Apache Guacamole – 深入探讨

如果我们深入研究我们之前看到的 Guacamole 网关的网络架构,我们会看到以下内容:

guac_pe_fig_2

图 12: Apache Guacamole 架构的重点视图。

对于权限提升,我们的重点是以下两个组成部分:

  • guacamole-client – 标记为Web Server。

  • guacamole-server – 标记为Proxy。

guacamole客户端

guacamole-client 组件负责执行用户身份验证的 Web 服务器。该 Web 服务器保存每个用户会话所需的配置,存储如下信息:

通缉协议——通常是 RDP。
网络内工作人员 PC 的 IP 地址。

等等。

客户端成功通过身份验证后,guacamole-clientguacamole-server 发起 Guacamole 协议会话,为客户端创建匹配会话。这是通过连接到guacd进程正在侦听的TCP 端口 4822(默认情况下)上的 guacamole-server 来完成的。

创建会话后,guacamole-client 仅在 guacamole-server 和客户端浏览器之间来回传递信息。

guacamole服务器

根据 Apache 的文档:“guacdGuacamole 的核心。” 启动时,guacd侦听 TCP 端口 4822 并等待来自 guacamole-client 的传入指令。请务必注意,此端口上的通信不使用身份验证或加密(可以启用 SSL,但它不是默认设置)。为此,我们在图 12 中添加了两个防火墙,它们应该负责限制对这个 TCP 端口的访问,只允许 guacamole-client 连接。

建立连接后,guacd创建一个新线程并调用负责启动 Guacamole 协议的函数。此时,有两个用户选项:

创建新连接。
加入现有连接

旁注:我们使用术语connection而不是session,因为这是 Guacamole 用来指代与给定计算机的连接的术语。每台计算机都有一个连接,多个用户可以共享同一个连接。没有“用户会话”,因为整个设计基于与给定计算机的 Guacamole 连接,用户只需加入连接即可。

第一种选择是迄今为止使用最广泛的。在这种情况下,会为新创建的连接生成一个随机唯一id (UUID),并为其生成一个fork()ed 进程。UUID 和新进程之间的映射存储在一个名为的内存字典中proc-map,并且 UUID 被发送回 guacamole 客户端。需要注意的是,生成的进程会在启动与网络内部计算机的连接之前立即放弃其权限。

第二个选项非常独特,可能是为了让多个用户可以共享一个连接并一起工作而实现的。在这种情况下,用户通过提供连接的 UUID 请求加入现有连接。为了区分用户,创建连接的用户是“所有者”,其他用户将“所有者”设置为false。此选项还包括为未标记为“所有者”的用户建立只读连接的可能性。

guac_pe_fig_3

图 13:添加新用户并将过程存储在 中proc-map以允许其他人加入。

为了支持加入用户,给定连接的衍生进程继承了用于与父guacd进程通信的套接字对。当主线程初始化所需的客户端时,例如,用于 RDP 连接的 FreeRDP,另一个线程等待来自父进程的消息,向我们的进程发出新用户要求加入连接的信号。

该guacd进程充当产生每个连接进程的连接管理器,同时也为这些产生的进程实现核心逻辑。因此,从现在开始,我们将父guacd进程称为主进程。

权限提升 – 分步

步骤 0 – 接管单个 guacd 进程

我们已经有了这部分的有效利用。

步骤 1 – 伪装成guacamole客户

虽然guacd我们控制的进程只是在网关内运行的低权限进程,但它仍然具有一些有用的权限。首先,在网关上运行使我们能够通过 TCP 端口 4822 连接到主进程。由于主进程不希望通过此端口进行身份验证,因此没有什么可以阻止我们像普通的guacamole客户端一样连接到它并控制进程.

步骤 2 – 从我们的记忆中获取秘密

这是我们要利用的关键设计选择。由于guacd可执行文件包含主进程和每个连接进程的逻辑,因此当产生新的连接进程时,只fork()使用它。这句话值得重复:只 fork()使用,不使用execve()!

这是什么意思?分叉进程包含其父进程的整个内存快照,并且在execve()调用时该快照将替换为新映像。如果没有这个关键调用,子进程将继承其父进程的整个内存地址空间。这包括:

全内存布局——当我们想要攻击父进程时,对于绕过 ASLR 很有用。
完整的内存内容——存储在主进程中的每个秘密也被提供给子进程。

这意味着我们的进程具有proc-map映射每个秘密连接 UUID 到其各自进程的映射。我们只需要在我们的内存中找到这个数据结构,我们就会拥有所有当前活动的 UUID

定位 proc-map 本身纯粹是技术性的。在我们的漏洞利用中,我们通过从/proc/<pid>/maps. 数据结构如此之大,以至于被mmap()分配到独立的内存分配中,因此它在文件中有自己的条目。

步骤 3 – 加入所有会话

我们已经让主要流程确信我们可以发起 Guacamole 协议请求,现在我们甚至知道要请求哪些请求。我们的下一步是通过提供它们现在已知的 UUID 来请求加入每个现有连接。

guac_pe_fig_4

图 14:标记我们成功加入现有连接的日志条目。

令人惊讶的是,“只读”会话属性是由 guacamole-client 设置的。这意味着虽然我们不是连接的所有者,但我们仍然可以为加入的用户关闭“只读”权限位并获得连接的完全权限。此外,除了主进程中的日志消息(如图 14 所示)之外,没有其他用户刚刚加入连接的可见迹象。

步骤 4 – 重复

如果您密切关注,您可能已经注意到我们的攻击计划中的一个缺点:我们的guacd过程有一个过时的proc-map映射图像。在我们生成后开始的任何会话只会在真实中更新proc-map,因此在我们过时的内存映像中将不可用。

这个缺点有一个简单的解决方案。每个选定的时间间隔,比如 5 分钟,我们都可以向主进程发送命令以启动新的 RDP 连接并连接到网络内受感染的机器。这样,guacd就会产生一个新的,并且现在拥有更新版本的proc-map. 使用我们的原始漏洞,我们也可以攻击这个过程,并“刷新”我们的proc-map.

当链接在一起时,这里是完整的漏洞利用链,RCE + PE,正在运行: https://youtu.be/vm8i9jJ2Np0

披露时间表

2020331 日 – 向 Apache 披露了漏洞。
2020331 日 – Apache 做出回应并要求提供更多信息。
2020331 日 – 向 FreeRDP 披露了漏洞。
2020331 日 – FreeRDP 做出回应并要求提供更多信息。
2020331 日——FreeRDP 通知我们 CPR-ID-2145CPR-ID-2156 是重复的,因为它们已经在 2020330 日单独报告过。(我们运气不好。)
202058 日 – Apache 在推送到其 GitHub的静默提交中修补了漏洞。
2020510 日 – 我们通知 Apache,他们的补丁修复了所有报告的漏洞。
2020512 日 – Apache 向报告的 4 个漏洞发布了 2CVE-ID
2020628 日 – Apache 发布了官方补丁版本 – 1.2.0

结论

我们展示了反向 RDP 攻击场景的新角度,这是我们最初在 2019 年初提出的攻击场景。虽然用户通常认为 RDP 客户端……嗯……作为客户端,但 Apache Guacamole 场景告诉我们其他情况。在标准情况下,攻击者可以利用客户端中的漏洞来控制单个公司计算机。但是,当部署在网关内时,此类漏洞对组织的影响要严重得多。

虽然在 COVID-19 大流行的艰难时期,在家远程工作的过渡是必要的,但我们不能忽视这种远程连接的安全影响。使用 Apache Guacamole 作为我们的示例目标,我们能够成功演示如何使用组织内部的受感染计算机来控制处理所有远程会话到网络的网关。一旦控制了网关,攻击者就可以窃听所有传入会话,记录所有使用的凭据,甚至启动新会话以控制组织内的其余计算机。当大部分组织都在远程工作时,这个立足点就相当于获得了对整个组织网络的完全控制权。

我们强烈建议每个人确保所有服务器都是最新的,并且任何用于在家工作的技术都经过全面修补以阻止此类攻击企图。在我们的案例中,在发现漏洞并证明它们确实可以利用后的 24 小时内,我们实施了安全修复程序,并成为第一个针对此安全漏洞进行保护的生产环境,从而确保我们的员工可以安全地远程连接。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1605/