在本系列的第一部分中,我们为读者详细展示了如何使用AFL++在三个开源的FTP服务器(Pure-FTPd,Bftpd和ProFTPd)中挖掘安全漏洞的。同时,我们还详细介绍了执行模糊测试所需的代码更改,以及测试过程中发现的三个最有趣的漏洞。
在第二部分中,我们将深入研究针对FreeRDP的模糊测试。众所周知,FreeRDP是远程桌面协议(RDP)的开源实现,其应用非常的广泛的。与上一篇文章类似,我们将对完整的模糊测试过程进行详尽的演示!
让我们开始进行模糊测试吧!
背景介绍
2020年4月,GitHub安全实验室进行了为期三天的内部黑客马拉松,目的是在Apache Guacamole中挖掘并修复尽可能多的漏洞。实际上,Apache Guacamole就是一个浏览器应用程序,可让您通过VNC、RDP和SSH等标准协议访问远程计算机。特别是在当前的在家办公环境中,该应用变得越来越流行。
尽管黑客马拉松只持续了三天,但它为后续的模糊测试工作埋下了种子。 在黑客马拉松期间,每个团队成员都专注于代码的不同部分。就我而言,我选择专注于远程桌面协议(RDP)协议。
对于不熟悉RDP的读者来说,我们可以简单说一下。这是一个协议,允许用户使用图形界面来远程管理Windows计算机,就像实际上坐在机器前一样。
测试结果
这项研究的成果是,我在FreeRDP中找到了12个安全漏洞,具体如下所示:
CVE GHSL 说明 PoC
CVE-2020-13396 GHSL-2020-100 在ntlm_read_ChallengeMessage中发现OOB读取漏洞 PoC
CVE-2020-13397 GHSL-2020-101 在FreeRDP FIPS例程发现空指针引用漏洞 PoC
CVE-2020-13398 GHSL-2020-102 在FreeRDP crypto_rsa_common中发现堆溢出漏洞 PoC
CVE-2020-11099 GHSL-2020-103 在license_read_new_or_upgrade_license_packet中发现OOB读取漏洞 PoC
CVE-2020-11097 GHSL-2020-104 在FreeRDP ntlm_av_pair_get中发现OOB读取漏洞 PoC
CVE-2020-11098 GHSL-2020-105 在FreeRDP glyph_cache_put中发现OOB读取漏洞 PoC
CVE-2020-4030 GHSL-2020-106FreeRDP中因整数符号不匹配导致OOB读取漏洞 PoC
CVE-2020-11096 GHSL-2020-107 在FreeRDP update_read_cache_bitmap_v3_order中发现OOB读取漏洞 PoC
CVE-2020-11095 GHSL-2020-124 在FreeRDP update_recv_primary_order中发现OOB读取漏洞 PoC
CVE-2020-4032 GHSL-2020-125 FreeRDP中的整数符号不匹配导致OOB读取漏洞 PoC
CVE-2020-4033 GHSL-2020-128 在FreeRDP RLEDECOMPRESS中发现OOB读取漏洞 PoC
CVE-2020-4031 GHSL-2020-129 在gdi_SelectObject中发现UAF漏洞 PoC
创建“自定义”输入用例
在开始模糊测试之前,我们需要(至少)要创建一个具有代表性的输入用例。当然,这里的代表性的意思是达到高代码覆盖率的序列化RDP数据包跟踪。
虽然最快的方法是重用以前捕获的数据包数据(例如来自Wireshark的数据包),但在这种情况下,我选择从头开始创建自己的输入用例。当然,这里说的从头开始,意思是仅使用GDB和十六进制编辑器从头开始搞起。
尽管此方法显然较慢且较费力,但它的优点是迫使我们熟悉目标代码库。由于进行模糊测试的网络目标,通常需要进行必要的代码修改,因此,我们在手工制作RDP数据包时获得的知识,将对后续代码的修改带来很大的帮助。
手工制作输入用例数据包的另一个重要优点是,它使我们的标准布局和结构与官方客户实施的有所不同。通过从头开始构建自己的输入用例,我们可以更轻松地构建不符合规范的数据包结构,并可能产生更好的模糊测试结果。
输入用例:RDP/TPKT.bin
PDU
接下来,我将介绍RDP协议中的一些最重要的结构。
首先,我们应该将RDP连接视为协议数据单元(PDU)的序列。这些PDU有两种不同的类型:
TPKT数据包(Slow-Path):TPKT报头的长度固定为4,随后的X.224 TPDU的长度至少为3个字节。因此,最小TPKT长度为7,最大TPKT长度为65535。由于TPKT长度包括TPKT报头(4个字节),因此,X.224 TPDU的最大长度为65531。
Fast-Path数据包:Fast-Path将从第一个字节开始检查服务器输出数据包,目的是提高带宽。TPKT报头、X.224 TPDU和MCS Send Data Indication将被替换;Security Header将被压缩到快速路径输出报头中;Share Data Header将被新的快速路径格式所取代。
简而言之,Slow-Path数据包始终以TPKT报头开始,该报头以字节0x03开始,而Fast-Path数据包则会将第一个字节的前两个最低有效位清零。
除此之外,我们还必须考虑启用NLA模式的情况。网络级身份验证(NLA)是RDP的一项功能,要求连接的用户在与服务器建立会话之前进行身份验证。在这些情况下,我们还必须知道TSRequest结构,具体如下所示:
TSRequest结构是CredSSP客户端和CredSSP服务器使用的最多的一种结构。它不仅用于保存SPNEGO令牌,并且还可能保存在客户端和服务器之间传递的Kerberos/ New Technology LAN Manager(NTLM)消息,以及用于绑定到TLS会话的公钥身份验证消息委托给服务器的客户端凭据。需要注意的是,TSRequest消息总是通过CredSSP协议交换中的客户端和服务器之间的TLS加密通道发送。
RDP的状态
RDP是一个非常复杂的协议,在完全建立连接之前,需要处理许多不同的请求和状态。这就意味着需要检查大量协议约束。
俗话说得好,一图胜千言,因此,下面就用图片来描述RDP的连接过程:
除此之外,读者还可以通过下面的链接找到有关RDP协议的详细信息:https://docs.microsoft.com/zh-cn/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee。
RDP协议的FreeRDP实现通过CONNECTION_STATE定义了以下状态:
enum CONNECTION_STATE
{
CONNECTION_STATE_INITIAL,
CONNECTION_STATE_NEGO,
CONNECTION_STATE_NLA,
CONNECTION_STATE_MCS_CONNECT,
CONNECTION_STATE_MCS_ERECT_DOMAIN,
CONNECTION_STATE_MCS_ATTACH_USER,
CONNECTION_STATE_MCS_CHANNEL_JOIN,
CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT,
CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE,
CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT,
CONNECTION_STATE_LICENSING,
CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING,
CONNECTION_STATE_CAPABILITIES_EXCHANGE,
CONNECTION_STATE_FINALIZATION,
CONNECTION_STATE_ACTIVE
};
上面定义的所有结构,都将反映在我们的模糊测试字典(fuzzing dictionary)中。
为进行模糊测试对代码进行相应的修改
正如我们在上面看到的,RDP协议实现了许多完整性检查和限制,目的是避免由于数据流中的错误而导致意外失败。
但是,出于我们的目的,这种完整性检查将带来许多阻碍,因为我们的Fuzzer会不断更改数据流字节,从而触发校验和失败,这通常会导致RDP连接中止。为了绕过这些限制,我们必须对FreeRDP的代码进行一些必要的修改。
下面,我们将详细介绍具体的修改过程。
禁用RC4加密
首先,我在“许可证交换”阶段禁用了RC4加密。从服务器到客户端的许可证传输就是在这个阶段进行的——客户端会存储许可证,并在后面建立连接时将许可证发送到服务器以进行验证。
禁用RC4的原因是,诸如AFL之类的Fuzzer无法在加密流中找到相关的安全问题。这是因为翻转位/字节突变通常会破坏加密流。因此,生成的输入将是无效的,并且可能无法正确进行相应的解密。
libfreerdp/core/license.c
禁用TLS加密
正如我们对RC4加密所做的那样,出于类似的原因,我们还将禁用RDP连接上的任何TLS加密,其中包括握手和证书验证。
libfreerdp/crypto/tls.c
libfreerdp/core/nla.c
禁用MAC检查
消息验证码(Message Authentication Code,MAC)是数据的加密校验和,它使用会话密钥来检测是否对数据进行了无意和有意的修改。
由于Fuzzer突变可能会导致MAC检查失败,因此,我们也应该禁用这些检查。
libfreerdp/core/license.c
禁用NTLM签名验证
NTLM是在包含运行Windows操作系统的计算机的网络上使用的一种身份验证协议。并且,RDP协议将使用NTLM或Kerberos进行身份验证。
在本文中中,我们将主要关注NTLM身份验证。并且,由于NTLM协议包含签名验证过程,因此,Fuzzer在NTLM部分中所做的任何更改都将导致身份验证过程出错。所以,我们也需要禁用NTLM签名验证功能:
winpr/libwinpr/sspi/NTLM/ntlm.c
禁用Nhash验证
当启用网络级身份验证(NLA)时,我们还需要禁用SHA256摘要比较:
libfreerdp/core/nla.c
禁用多线程
在本系列的前一篇文章中,我们讨论了如何避免forking目标进程,因为AFL不能很好地处理多进程目标。
虽然这个限制不适用于多线程应用程序,但是目标进程中的多个线程可能会对AFL的稳定性产生负面影响。这主要是由于程序指令的执行顺序是不确定的,而这反过来又会导致更糟糕的代码覆盖率。
出于这个原因,我们的建议是:只要有可能,您就应该将线程并发限制在最低限度。下面,我们展示了为此目的在FreeRDP代码中所做的一些修改:
channels/drdynvc/client/drdynvc_main.c
channels/cliprdr/client/cliprdr_main.c
libfreerdp/codec/rfx.c
其他改动
此外,我们还对FreeRDP代码做了一些额外的小改动,这些改动与我们在前一篇文章中已经讨论过的要点基本一致。
您可以访问我的FreeRDP_FUZZ存储库,以获取我们为有效地对FreeRDP库进行模糊测试而所做修改的详细记录,具体请访问https://github.com/antonio-morales/FreeRDP_FUZZ/commit/3f295034cb6a16a51e9ee600dce61c206c5a72a0#。
开始进行模糊测试
既然我们已经对代码进行了必要的修改,接下来,我们就进入模糊测试阶段了。
首先,我们需要配置和构建需要通过AFL进行模糊测试的项目:
cmake -G "Eclipse CDT4 - Unix Makefiles" -DCHANNEL_URBDRC=ON -DWITH_FFMPEG=ON -DWITH_CUPS=ON -DWITH_PULSE=ON -DWITH_FAAC=ON -DWITH_FAAD2=ON -DWITH_GSM=ON -DWITH_JPEG=ON -DWITH_MBEDTLS=ON -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g" -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fno-sanitize-recover=all" DCMAKE_INSTALL_PREFIX=/home/antonio/Downloads/FreeFuzzing/FreeRDP/extLibs/FreeRDP/install/ -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" -DCMAKE_BUILD_TYPE=Debug,ASAN,UBSAN -DWITH_SSE2=ON -DMONOLITHIC_BUILD=ON -DBUILD_SHARED_LIBS=OFF .
cmake --build . -j 4
需要说明的是,我启用了Address Sanitizer和Undefined Behavior Sanitizer,并且将“afl-clang-fast”设置为默认编译器。此外,这里还禁用了共享库,并选择了整体构建选项。最后,这里将最大可能的使用外部函数(DCHANNEL_URBDRC、DWITH_CUPS、DWITH_PULSE、DWITH_FAAC、DWITH_GSM、DWITH_JPEG、DWITH_MBEDTLS)。
正如您在以下代码片段中看到的,我们将在模糊测试过程中调用xfreerdp可执行文件:
./client/X11/xfreerdp /v:127.0.0.1 /p:whatever /log-level:TRACE /relax-order-checks +glyph-cache +bitmap-cache +menu-anims @@
为了提高FreeRDP代码的覆盖范围,我还启用了以下命令行选项:
log-level:TRACE:将默认日志级别设置为最大级别
/relax-order-checks:不检查功能交换期间是否声明了RDP order,仅在连接到buggy服务器时使用
glyph-cache:启用Glyph-Cache(实验功能)
bitmap-cache:启用位图缓存
menu-anims:启用菜单动画
此外,我们还为Fuzzer提供了以下字典:RDP.dict。
最后,通过AFL对FreeRDP进行模糊测试的命令行如下所示:
ASAN_OPTIONS=verbosity=3,detect_leaks=0,abort_on_error=1,symbolize=0,debug=true,check_initialization_order=true,detect_stack_use_after_return=true,strict_string_checks=true,detect_invalid_pointer_pairs=2 afl-fuzz -t 1500 -m none -i ./AFL/afl_in/ -o './AFL/afl_out' -x './AFL/dictionaries/Basic.txt' -- ./client/X11/xfreerdp /v:127.0.0.1 /p:whatever /log-level:TRACE /relax-order-checks +glyph-cache +bitmap-cache +menu-anims @@
致谢
我要感谢akallabeth在修复安全漏洞过程中体现出的合作精神,并对其专业精神深感敬意。我想说,与您共事并致力于提高FreeRDP的安全性是一件非常愉快的事!
下面是在这篇文章中涉及的工具和参考资料:
FreeRDP Reference Documentation
Specifications for Remote Desktop Protocol: Basic Connectivity and Graphics Remoting