CVE-2020-0863:Windows Diagnostic Tracking Service任意文件读取漏洞分析

0x00 前言

虽然这个漏洞不能直接实现完整权限提升,不能以 NT AUTHORITY\SYSTEM 权限执行代码,但由于利用过程中涉及一些”小技巧“,因此还是比较有趣。Diagnostic Tracking Service(诊断跟踪服务,也称为Connected User Experiences and Telemetry Service)可能是比较有争议的一个Windows功能,该功能用来收集用户和系统数据。我在该服务中发现了一个信息泄露漏洞(这件事情本身就有点讽刺意味),利用该漏洞,本地用户可以在 NT AUTHORITY\SYSTEM 上下文中读取任意文件。

0x01 DiagTrack RPC接口

这里我们将重点从COM转到RPC(Remote Procedure Call,远程过程调用)上,我们可以使用RpcView查看Diagtrack提供的接口。

该服务公开了多个接口,这里我们重点关注的是UUID为 4c9dbf19-d39e-4bb9-90ee-8f7179b20283 的接口,该接口有37个方法,因此可能也存在更大的攻击面。

我找到的漏洞位于 UtcApi_DownloadLatestSetting 方法中。

0x02 UtcApi_DownloadLatestSettings方法

RpcView可以帮我们生成RPC接口对应的IDL(Interface Definition Language,接口定义语言)文件。经过编译后,我们可知 UtcApi_DownloadLatestSettings 对应的C函数原型如下所示:

long DownloadLatestSettings( 
    /* [in] */ handle_t IDL_handle,
    /* [in] */ long arg_1,
    /* [in] */ long arg_2
)

该函数第一个参数为RPC绑定句柄,剩下两个参数目前尚未澄清。

如果大家不熟悉RPC接口的工作方式,这里简单介绍一下。在处理远程过程调用时,我们首先要使用远程接口对应的唯一标识符(这里为 4c9dbf19-d39e-4bb9-90ee-8f7179b20283 )获取该接口对应的句柄。随后,我们可以使用该句柄来调用相应的方法。因此,远程方法的第1个参数通常为一个 handle_t 参数。这也是大部分接口的工作方式。

获得远程接口的绑定句柄后,我首先尝试使用如下参数调用该函数:

RPC_BINDING_HANDLE g_hBinding;
/* ... initialization of the binding handle skipped ... */
HRESULT hRes;
hRes = DownloadLatestSettings(g_hBinding, 1, 1);

然后我使用Process Monitor观察后台进行的文件操作:

虽然这个服务运行在 NT AUTHORITY\SYSTEM 上下文中,我注意到该服务会尝试枚举如下目录中的XML文件,而该目录的所有者为当前登录用户:

C:\Users\lab-user\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Tips\

我在测试环境中使用的用户为 lab-user ,该用户为具备标准权限的正常用户,不具备任何管理员权限。我们之所以能观察该操作,是因为目标服务调用了 diagtrack.dllFindFirstFileW()

默认情况下,这似乎是一个空文件夹,因此我在其中创建了一些XML文件:

然后再次运行测试程序,可以观察到如下结果:

这一次 QueryDirectory 操作能成功完成,目标服务会读取 file1.xml 的内容(该文件为目录中第一个XML文件),将其拷贝到 C:\ProgramData\Microsoft\Diagnosis\SoftLandingStage\ 目录中的一个新文件(文件名保持一致)。

目标服务会对其他2个文件( file2.xmlfile3.xml )执行相同的操作:

最后,服务会删除 C:\ProgramData\[…]\SoftLandingStage 中创建的所有XML文件。

注意:我在Procmon中创建了一个特定规则,能将 DeleteFile API调用上下文中涉及到 CreateFile 操作高亮标出。

上图中, CreateFile 操作源自于 diagtrack.dll 中的 DeleteFileW() 调用:

0x03 任意文件读取漏洞

服务在拷贝文件时,并没有调用 MoveFileW() 来实现文件移动,也没有调用 CopyFileW() 实现文件拷贝,并且我们不能控制目的文件夹,因此,本地攻击者无法利用该操作将任意文件移动/拷贝到任意目录。服务会读取每个文件的内容,将内容写入 C:\ProgramData\[...]\SoftLandingStage\ 目录中的新文件。从某种角度来看,这应该是一种手动文件复制操作。

这里我们能完全控制的一个因素为源文件目录,因为该目录所有者为当前登录的用户。我们还要注意到一点,目的文件夹的读取权限为 Everyone ,这意味着默认情况下, Everyone 组成员可以读取该目录中创建的新文件,因此我们仍然有可能滥用这种高权限文件操作。

比如,我们可以将 C:\Users\lab-user\AppData\Local\Packages\[…]\Tips 文件夹替换为指向对象目录(Object Directory)的一个挂载点(mountpoint),然后创建指向文件系统上任意文件的一个伪符号链接。

如果系统中存在一个备份SAM文件,我们可以创建如下符号链接,获得该文件的一个副本。

C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC\Control\file1.xml -> \??\C:\Windows\Repair\SAM

从理论上讲,如果该服务尝试打开 file1.xml ,就会被重定向到 C:\Windows\Repair\SAM 。因此,服务会读取该文件内容,将其拷贝到 C:\ProgramData\[…]\SoftLandingStage\file1.xml ,使得本地用户能读取该内容。是不是非常简单?然而并非如此。

这里我们会面临两个问题:

1、在 Tips 文件夹上调用 FindFirstFileW() 时会失败,因为目标挂载点并不是一个“真正的”目录。

2、整个过程结束时,服务会删除 C:\ProgramData\[…]\SoftLandingStage 中创建的 file1.xml 文件。

我们可以通过另一个挂载点来解决这两个问题,其中会涉及到一些诱饵文件以及机会锁(OpLock)。

0x04 解决FindFirstFileW()问题

为了利用前面介绍的服务行为,我们必须找到可靠的利用方法,将文件读取操作重定向到我们设置的任意文件。然而由于服务调用了 FindFirstFileW() ,这里我们无法直接使用伪符号链接。

注意:Win32 FindFirstFileW() 函数首先会在目标目录中枚举满足指定过滤条件的文件,但这种方式无法适用于对象目录。比如,我们可以执行 dir C:\Windows 命令,但无法执行 dir "\RPC Control" 命令。

第一个问题解决起来非常简单。我们可以不直接创建一个对象目录,而是首先创建指向实际目录的一个挂载点,该目录中包含一些诱饵文件。

首先,我们需要创建一个临时工作目录,结构如下所示:

C:\workspace
|__ file1.xml 
|__ file2.xml

然后,创建挂载点:

C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> C:\workspace

完成这些操作后, FindFirstFileW() 可以执行成功,返回 file1.xml 。此外,如果我们在该文件上设置一个OpLock,我们可以部分控制目标服务的执行流程(因为当远程过程尝试访问该文件时会暂停执行)。

当OpLock触发时,我们可以切换挂载点,指向对象目录。因为 QueryDirectory 操作只会在 FindFirstFileW() 调用开始时执行一次,因此这种操作能执行成功。

C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC Control\file2.xml -> \??\C:\users\lab-admin\desktop\secret.txt

注意:此时我们并不需要创建 file1.xml 的符号链接,因为目标服务已获取该文件的句柄。

因此,当服务打开 C:\Users\lab-user\AppData\[…]\Tips\file2.xml 时,实际上打开的是 secret.txt ,然后会将其内容拷贝到 C:\ProgramData\[…]\SoftLandingStage\file2.xml

总结一下:我们可以诱骗服务读取我们并不拥有的一个文件,然而这里我们会涉及到第二个问题:在操作完成时,服务会删除 C:\ProgramData\[…]\SoftLandingStage\file2.xml ,此时我们将无法读取该文件内容。

0x05 解决文件删除问题

由于目标文件会在操作完成时删除,因此我们必须赢得与目标服务的竞争条件,在服务执行删除操作前拿到文件的副本。为了完成该任务,我们可以有两种选择:第一个选择为采取暴力方式。我们可以实现一个监控机制,循环监控 C:\ProgramData\[…]\SoftLandingStage 目录文件夹,当 NT AUTHORITY\SYSTEM 完成新XML文件写入时,第一时间拿到文件副本。

然而,暴力方式并不是最佳选择。这里我们有更为可靠的第二种选择,但我们得从头考虑整个策略。

前面我们在临时的工作目录中创建了2个文件,这里我们要创建3个文件:

C:\workspace
|__ file1.xml
|__ file2.xml  
|__ file3.xml

下一个步骤相同。然而,当 file1.xml 上的OpLock触发时,我们将多执行两个操作。

首先,我们将切换挂载点,创建2个伪符号链接。然后,我们要确保 file3.xml 链接指向的是实际的 file3.xml 文件:

C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC Control\file2.xml -> \??\C:\users\lab-admin\desktop\secret.txt
\RPC Control\file3.xml -> \??\C:\workspace\file3.xml

然后,在释放第一个OpLock前,我们需要在 file3.xml 上设置一个新的OpLock。

采用这种策略后,目标服务的整个操作过程如下所示:

1、DiagTrack尝试读取 file1.xml ,触发第一个OpLock。

2、此时,我们切换挂载点,创建2个符号链接,在 file3.xml 上设置OpLock。

3、释放第一个OpLock( file1.xml )。

4、DiagTrack拷贝 file1.xmlfile2.xmlfile2xml 指向的是 secret.txt )。

5、DiagTrack尝试读取 file3.xml ,触发第二个OpLock。

6、这一步为关键步骤。此时,远程过程被暂停,因此我们可以拿到 C:\ProgramData\[…]\SoftLandingStage\file2.xml 的副本,该文件本身就是 secret.txt 的副本。

7、释放第二个OpLock( file3.xml )。

8、远程过程结束,删除3个XML文件。

注意:这种技巧之所以行之有效,是因为DiagTrack采用顺序执行方式来操作整个过程,每个文件会被依次拷贝,最后删除新创建的所有文件。

这种方式较为可靠,使普通用户能够获得 NT AUTHORITY\SYSTEM 能读取的任意文件的副本。我开发的PoC测试结果如下图所示:

0x06 参考资料

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章