Apache Guacamole多个漏洞分析

0x00 前言Apache Guacamole是较为流行的一种远程办公基础框架 , 在全球范围内已有超过1000万次的Docker下载 。在研究过程中 , 我们发现Apache Guacamole存在多个严重的反向RDP漏洞 , 并且也受我们在FreeRDP中找到的一些新漏洞的影响 。简而言之 , 如果攻击者成功入侵了某个组织的内部计算机 , 那么当正常用户尝试连接被控制的主机时 , 攻击者就可以利用这些漏洞来攻击Guacamole网关 。恶意攻击者可以完全控制guacamole-server , 拦截并控制其他已连接的会话 。
攻击过程可参考该视频 , 视频中我们成功利用这些漏洞控制Guacamole以及已连接的所有会话 。
 
0x01 Apache Guacamole简介随着疫情期间远程办公的兴起 , 我们认为针对远程办公的技术方案将变成一个热点的研究目标 , 因此选择Apache Guacamole作为这类解决方案的代表来研究 。前文也提到过 , 这款产品是市场上最重要的工具之一 。许多单位会使用该产品来连接网络 , 许多网络访问及安全产品也会在自身产品中集成Apache Guacamole , 比如Jumpserver Fortress、Quali、Fortigate等 。
经过一些背景调查后 , 我们绘制了典型的网络架构 , 如图1所示:

Apache Guacamole多个漏洞分析

文章插图
 
图1. 部署Apache Guacamole网关的典型网络架构
在这种场景中 , 员工使用浏览器连接公司在互联网上开放的服务器 , 进行身份认证 , 然后就可以访问公司内的主机 。员工在使用浏览器时 , guacamole-server会选择某种支持的协议(RDP、VNC、SSH等) , 使用开源客户端连接企业网内的特定主机 。连接成功后 , guacamole-server会充当中间人角色 , 来回传递事件 , 将数据从所选的协议转换为特定的“Guacamole Protocol” , 反之亦然 。
了解该架构后 , 我们可以考虑一些攻击场景:
1、反向攻击场景:企业网内部被入侵的一台主机可以利用入站连接来攻击网关 , 希望能控制目标网关 。
2、恶意员工场景:恶意员工使用网内主机 , 利用链路两端节点的控制权来控制目标网关 。
 
0x02 是否需要0-Day在深入研究代码之前 , 我们先简单了解一下FreeRDP 。在之前针对反向RDP攻击的研究中 , 我们找到了这款RDP客户端中的一些漏洞 , 攻击者可以通过恶意RDP“服务器”来利用该漏洞 。换而言之 , 企业网内的恶意主机可以控制连接该主机的正常FreeRDP客户端 。我们提供了针对某个漏洞(CVE-2018-8786)的基本PoC , 也演示了RCE(远程代码执行)场景 。
观察Apache Guacamole的发行版本 , 可以看到只有2020年1月底发布的1.1.0版中增加了对最新版FreeRDP(2.0.0)的支持 。由于FreeRDP只在2.0.0-rc4版中修复了我们发现的漏洞 , 这意味着2020年1月之前发布的所有版本使用的都是存在漏洞的FreeRDP版本 。
因此我们对0-Day漏洞的挖掘暂告一段落 , 推测大多数企业可能还没有升级至最新版本 , 可以使用已知的1-Day漏洞进行攻击 。然而我们还是决定再次挖掘RDP协议中的漏洞 , 具体研究对象包括:
1、guacamole-server代码 , 只关注其中对RDP协议的支持代码 。
2、最新版FreeRDP的代码:2.0.0-rc4版 。
我们设想的利用条件限定于默认安装的版本 , 也就是说 , 只能使用默认情况下处于启用状态的功能 , 并且希望不需要与客户端进行任何交互 。
 
0x03 寻找新漏洞由于我们对FreeRDP的代码非常熟悉 , 对RDP整体也比较了解 , 因此很快就找到了一些漏洞 。
CPR-ID-2141:信息泄露漏洞
CVE编号: CVE-2020-9497
文件:protocolsrdpchannelsrdpsndrdpsnd-messages.c
函数:guac_rdpsnd_formats_handler()
备注:由于Apache并没有将我们报告的漏洞(CPR-ID)与他们公布的CVE-ID一一对应 , 因此我们主要根据他们提供的CPR-ID来对应具体漏洞 , 这样更准确一些 。
为了在RDP连接以及客户端之间中继消息 , 开发者为默认RDP通道实现了一种扩展 。其中有个通道负责获取服务端的音频 , 因此被称之为rdpsnd(RDP Sound) 。
然而在实际场景中 , guacamole-server与FreeRDP之间的集成节点很容易出错 。传入的消息经过FreeRDP的wStream对象封装 , 因此相应的数据应该使用该对象的API来解析 。然而如图2所示 , 开发者忘了设置一个强制条件:传入的流对象所包含的字节数必须与数据包所声明的字节数相匹配 。


推荐阅读