0x00 前言
Remote Desktop Gateway(RDG,远程桌面网关)之前名为“Terminal Services Gateway”,是为Remote Desktop (RDP,远程桌面)提供路由的一种Windows Server组件。在RDG应用场景中,用户并没有直接连接到RDP服务器,而是连接网关,通过网关的身份认证。认证成功后,网关会将RDP流量转发至用户指定的地址,因此网关实际上扮演的是代理角色。在这种场景下,只有网关需要对互联网开放,其他所有RDP服务端可以安全躲在防火墙后面。由于RDP是更为庞大的攻击面,因此正确设置RDG环境后,我们可以显著减少可能存在的攻击面。
在2020年1月的安全更新中,微软修复了RDG中存在的2个漏洞。漏洞编号分别为CVE-2020-0609及CVE-2020-0610,这两个漏洞都可以实现预认证远程代码执行效果。
0x01 Diff分析
首先我们可以分析受影响的DLL在打补丁之前和打补丁之后存在哪些差异点。
图1. BinDiff分析打补丁前后的RDG可执行文件
经过分析后,我们发现只有1个函数被修改过。RDG支持3种不同的协议:HTTP、HTTPS以及UDP,更新版的函数负责处理负责处理UDP协议。正常情况下,我们可以并排比较打补丁前后的相关函数。然而不幸的是,这个代码非常庞大,有多处改动。因此为了便于大家理解,这里我们直接给出该函数对应的伪代码,剔除了其他不相关的代码。
图2. 处理UDP报文的函数伪代码片段
RDG UDP协议允许将较大的消息拆分为多个独立的UDP报文。由于UDP属于无连接协议,因此报文可以乱序到达。该函数的功能是重组消息,确保每个报文都位于正确的位置上。每个报文中都包含一个头部数据,其中包含如下字段:
-
fragment_id
:报文在序列中的具体位置; -
num_fragments
:序列中报文的总数; -
fragment_length
:报文数据长度。
消息处理函数使用报文头部数据来确保收到的消息能以正确的顺序重组,不会缺失任何消息。然而,该函数在实现上存在一些bug,可以为攻击者所用。
0x02 CVE-2020-0609
图3. 报文处理函数中的边界检查逻辑
memcpy_s
会将每个分片(fragment)拷贝到重组缓冲区的某个偏移量,重组缓冲区在堆上分配,每个分片的偏移量由fragment_id
乘以1000
计算得出。然而,这里的边界检查逻辑并没有考虑到偏移量这个因素。这里我们可以假设buffer_size
等于1000
,然后我们发送包含2个分片的消息。
1、第1个分片(fragment_id=0
)长度为1
。此时this->bytes_written
等于0
,因此可以通过边界检查。
2、代码将1
字节数据写入偏移量为0
的缓冲区地址,bytes_written
递增1
。第2个分片(fragment_id=1
)长度为998
,此时this->bytes_written
等于1,而1 + 998
仍然小于1000
,因此可以通过边界检查。
3、代码将998
字节数据写入偏移量为1000
(fragment_id*1000
)的缓冲区地址,导致在缓冲区末尾后写入998
个字节。
需要注意的是,报文不一定按顺序发送(UDP协议)。因此如果我们发送的第1个报文中fragment_id=65535
(最大值),那么就会被写入偏移量为65535*1000
的地址,也就是缓冲区末尾后的65534000
个字节。攻击者可以修改fragment_id
,这样就有可能将最多999
字节数据写入缓冲区后偏移量为1
到65534000
的任何地址。与典型的线性堆溢出漏洞相比,这个漏洞要更加灵活。该漏洞不单单可以用来控制写入数据的大小,也能控制写入数据的位置。如果附加额外控制技术,攻击者可以实现更为精准的数据写入,避免出现不必要的数据破坏现象。
0x03 CVE-2020-0610
图4. 报文处理函数中标记哪些分片已接收成功
上图中的类对象维护了32位无符号整数组成的一个数组(每个数组元素对应1个分片),当收到1个分片后,相应的数据元素值会从0
修改为1
。当所有元素都被设置为1
时,代码完成消息重组,开始处理整个消息。该数组最多容纳64
个元素,但fragment_id
的取值范围为0
到65535
。代码唯一执行的验证操作就是确保fragment_id
值小于num_fragments
(后者同样可以被设置为65535
)。因此,我们可以将fragment_id
设置为65
到65535
之间的任意值,将在数据边界外写入1
(TRUE
)。虽然只将1个值设置为1
可能很难实现RCE效果,但即使最小的改动也可以对程序正常行为带来巨大的影响。
0x04 缓解措施
如果出于某种原因,用户无法安装补丁,此时仍然可以防御攻击者利用这类漏洞。RDG支持HTTP、HTTPS及UDP协议,但漏洞只存在于UDP报文的处理代码中。因此,用户可以简单禁用UDP传输功能,或者使用防火墙阻止UDP端口(通常为3391
),就足以防御这些漏洞。
图5. RDG设置