0x00 前言
2019年,我们发表了关于RDP攻击的两篇文章:Part 1及Part 2。在这些文章中,我们描述了常见远程桌面协议(RDP)客户端中的各种严重漏洞。此外,我们还重点关注了微软RDP客户端中的路径遍历(Path-Traversal)漏洞,攻击者可以利用该漏洞,在Hyper-V管理器中实现从客户机到宿主机的VM逃逸。
在分析这些漏洞是否适用于MacOS上的微软RDP客户端时,我们发现了比较有趣的一个结论:我们不仅可以绕过微软的补丁,也能绕过遵循微软建议的所有路径规范化检查机制。
注意:绕过微软核心路径遍历检查的漏洞目前仍未被修复,大家可以在本文结尾处的微软官方回应中了解更多详细信息。由于该漏洞存在不少安全隐患,我们强烈建议所有软件开发者和研究人员关注该漏洞严重性,确保自己的软件项目已被手动修复。
0x01 微软补丁分析
在分析RDP攻击的第二篇文章中,我们详细分析了微软解决该漏洞(CVE-2019-0887)所发布的补丁。该补丁相对比较简单,因为路径遍历漏洞根源在于没有对输入的FileGroupDescriptorW
剪贴板格式的文件路径进行过滤检查。微软采用了自己建议的最佳实践,使用PathCchCanonicalize函数添加了一个校验环节,如图1所示:
图1. 使用PathCchCanonicalize
计算每个文件名的规范格式
如果执行成功,系统随后会将规范化后的输出与原始文件名比较,只要两者不一致,就会出现错误。这意味着如果我们的文件名中包含.
或者..
形式的字符串,那么在转为规范化形式时就会被修改,从而无法通过有效性检查。
当时我们认为该漏洞的确已被修复,也在之前的文章中提到:“……该补丁符合我们的预期,修复了我们发现的路径遍历漏洞”。
0x02 macOS RDP客户端
在分析各种RDP客户端的时候,我们重点关注的是rdesktop、FreeRDP以及微软内置的客户端(Mstsc.exe
)。当时我们并没有分析macOS上的RDP客户端,因为我们认为macOS用户经常会使用前面提到的开源客户端。令人惊讶的是,当这次研究“结束”时,来自Mnemonic的Chris Risvik联系了我们。观看我们在Black Hat上的演讲并阅读我们的研究文章后,Chris开始研究macOS平台上的微软RDP客户端,并且找到了一个有趣的行为。根据这个行为,Chris认为CVE-2019-0887同样适用于微软的RDP客户端,因此与我们取得联系。
了解这些信息后,我们决定进一步研究。之前研究的时候我们已经设置了利用路径遍历漏洞的环境,因此现在我们只需要使用Mac主机,然后使用macOS平台上的微软RDP客户端来连接我们已有的恶意RDP服务器,此时事情开始变得有趣起来。
在等待我们的macOS研究员到达实验室时(他那天竟然迟到了),我决定更新服务端上的漏洞利用程序配置。之前我们使用的是Windows路径(A\B\C
),由于我们即将遍历macOS路径,因此我认为可以在利用代码中使用正斜杠(即A/B/C
),而不是之前的反斜杠。
在修改利用代码配置文件后,我们使用2个Windows主机本地测试了一下,判断是否一切正常。原始的利用代码(使用\
)成功被微软的补丁所阻拦,导致explorer.exe
被卡住。出于好奇,我们还测试了修改版的利用代码(使用/
),然后发现漏洞竟然利用成功了。只要在我们的恶意RDP服务器上将\
替换为/
,就能绕过微软的补丁!
注意:随后我们测试了macOS RDP客户端,发现该客户端的实现更加安全,不受CVE-2019-0887漏洞影响。从这一点来看,我们永远无法预测研究的最终成果究竟如何,也不一定知道整个研究过程中能发现哪些漏洞。
0x03 PathCchCanonicalize
在这个非预期行为的驱使下,我们决定编译一个简单的测试程序,只关注微软主要的路径规范函数:PathCchCanonicalize
。原始的检查逻辑如图2所示:
图2. 使用包含反斜杠(\
)的路径再次测试PathCchCanonicalize
如预期所示,程序会输出“The canonical string is: Evil.bat, and the status code: 0”。
随后,我们使用带有正常斜杠(/
)的路径来测试,如图3所示:
图3. 使用带有正斜杠(/
)的路径再次测试PathCchCanonicalize
令人惊讶的是,输出结果为:“The canonical string is: ../../Evil.bat, and the status code: 0”。
这似乎意味着PathCchCanonicalize
函数会忽略正斜杠字符,而该函数正是Windows在最佳实践指南中建议使用的函数!我们随后逆向分析了微软对该函数的实现,发现该函数只会搜索\
来拆分路径,忽略/
字符。
而该漏洞的影响应当不仅限于RDP,我们专门研究了微软对尝试执行目录遍历操作(如..
)时提供的建议,找到了一个函数:PathCcCanonicalize
。MSDN中提到了这个函数的作用,但由于可能存在缓冲区溢出漏洞,因此该函数实际上已被遗弃:
图4. MSDN上解释如何使用WinAPI来阻止目录遍历攻击
官方文档明确建议开发者使用PathCchCanonicalize
函数,然而根据前文所述,我们可以使用正斜杠(而不是反斜杠)来绕过该函数。
我们向自己内部的软件开发者提出了一个挑战任务:实现一个函数,输入为文件路径,验证文件能否写入预定的某个文件夹中。换句话说,我们的开发者要使用Windows API以及微软的最佳实践来阻止路径遍历攻击。不出所料,我们得到的典型代码如图5所示:
图5. 简单的路径过滤代码,始终使用的是PathCchCanonicalize
。
也就是说,在遵循微软的最佳实践时,即使是高级的开发者也会使用存在漏洞的函数,并且希望该函数能够同时处理以/
和\
分隔的路径。这意味着全世界可能有许多项目会受路径遍历攻击影响,因为这些开发者都比较信赖微软提供的存在漏洞的路径规范化函数。
0x04 Windows路径
在基于Unix的系统上,系统使用的是正斜杠(/
),而传统Windows使用的字符是反斜杠(\
)。然而从早期版本的Windows开始,该系统就已经同时支持/
以及\
。因此,在执行任何文件操作(访问/读/写)时,都可以兼容这两个分隔符。
正常情况下,我们会采用特定检查逻辑来保护我们的目标,但攻击者可能会使用不同的逻辑来绕过检查,这个漏洞就是一个典型的案例。Windows核心功能的开发者在处理文件路径时,不知道因为什么忽略了Windows对/
字符的支持,反而自己重新实现了文件路径逻辑,并且只支持\
分隔符。
0x05 CVE-2020-0655
我们再次与微软联系,提示官方并没有正确修复CVE-2019-0887,攻击者可以利用我们在PathCchCanonicalize
中新发现的漏洞来绕过该补丁。微软确定了该漏洞,在二月份的周二补丁日发布了新的补丁:CVE-2020-0655。
PathCchCanonicalize
的实现位于KernelBase.dll
中,我们发现本月的Windows更新竟然没有更新这个组件。如图6所示,kerberos.dll
在二月份更新中被更新过,而KernelBase.dll
依然保持原样。
图6. 根据Windows的.dll
文件时间戳,我们发现KernelBase.dll
并没有被更新
我们过滤出了在此次补丁中被更新的所有文件,发现其中有个mstscax.dll
。如果按照我们的猜测,微软可能只更新了针对RDP客户端的攻击场景,忽略了被广泛使用的存在漏洞的API函数。
之前针对CVE-2019-0887的补丁中添加了一个CFormatDataPacker::ValidateFilePaths
函数,在这次更新中,该函数又重新被修改过,如图7所示:
图7. 在RDP客户端补丁中会将/
替换为\
我们最初的猜测非常准确:微软更新了针对CVE-2019-0887漏洞的补丁,但PathCchCanonicalize
函数仍然受漏洞影响。
我们想搜一下是否已经有安全公告来提醒用户不要使用存在漏洞的路径规范化函数,但并没有任何收获。MSDN仍然建议用户在过滤恶意输入时要使用这个函数。
然而需要注意的是,这个补丁之所以有意义,是因为RDP中的文件处理与其他的WinAPI不同。我们检查了处理文件时常用的WinAPI(比如CreateFile
、DeleteFile
、CreateFolder
等),发现这些API都能够接受/
以及\
,这意味着RDP案例并不是独一无二的案例。
因此,在我们看来,如果想避免在使用/
分隔符时出现路径遍历攻击,可以考虑patch PathCchCanonicalize
。
0x06 官方回应
分析微软补丁后,我们联系了MSRC,提供了我们的研究成果以及这篇文章的预计发布时间。然而到目前为止,我们并没有得到官方的评论。
0x07 总结
首先,由于官方对RDP漏洞的修复存在问题,带来一些严重影响,我们建议所有用户确保已经安装了微软提供的补丁。
然而,在分析微软对CVE-2020-0655漏洞的补丁时, 我们发现该补丁并没有解决PathCchCanonicalize
函数中的核心问题。我们担心未来还会出现与RDP类似的攻击场景,由于Windows核心路径校验函数存在简单的绕过方法,可能会对其他软件产品构成严重风险。
正因为如此,我们强烈建议所有软件开发者以及安全研究人员关注该漏洞,确保自己的软件项目已手动打上补丁。
我们并不清楚为何这么一个简单的路径遍历绕过问题多年以来一直能存在于微软的核心路径校验功能中。我们怀疑许多开发者会选择重复造轮子,这样在渗透测试过程中我们才能发现许多特定实现中存在的错误。否则,只要以大型系统中负责处理文件路径的C代码为目标,常规渗透测试应该能够发现这个路径遍历绕过漏洞。