早在 2020 年 5 月,微软就修复了一个 Windows Print Spooler 权限提升漏洞。该漏洞的编号为 CVE-2020–1048,微软承认 SafeBreach Labs 的 Peleg Hadar 和 Tomer Bar 报告了该安全问题。在补丁发布的同一天,Yarden Shafir和Alex Ionescu发表了关于该漏洞的技术文章。本质上,用户可以通过创建指向磁盘上文件的打印机端口来写入任意文件。在修补漏洞 (CVE-2020–1048) 后,Print Spooler 现在将在添加端口之前检查用户是否有权创建或写入文件。补丁和博文发布一周后,Paolo Stagno(又名 VoidSec)私下向微软披露了 CVE-2020–1048 的绕过方法。该绕过在三个月后的 2020 年 8 月得到修补,微软承认有八个独立实体报告了该漏洞,该漏洞被确定为 CVE-2020-1337。该漏洞的绕过使用目录连接(符号链接)来规避安全检查。假设用户创建了目录C:\MyFolder\
并配置了一个打印机端口以指向该文件C:\MyFolder\Port
。该操作将被授予,因为确实允许用户创建C:\MyFolder\Port
. 现在,如果用户随后变成C:\MyFolder\
指向C:\Windows\System32\
创建端口之后的目录连接会发生什么?好吧,Spooler 会简单地写入文件C:\Windows\System32\Port
。
这两个漏洞 CVE-2020-1048 和 CVE-2020-1337 分别于 2020 年 5 月和 2020 年 8 月修补。2020 年 9 月,Microsoft 修补了 Print Spooler 中的另一个漏洞。简而言之,该漏洞允许用户通过SpoolDirectory
在打印机上配置属性来创建任意且可写的目录。补丁是什么?SpoolDirectory
几乎相同的故事:在补丁之后,Print Spooler 现在会在设置打印机属性之前检查用户是否有权创建目录。也许您已经可以看到这篇文章的去向。让我们从头开始。
Windows Print Spooler 是所有 Windows 工作站和服务器上的内置组件,它是打印界面的主要组件。Print Spooler 是管理打印过程的可执行文件。打印管理包括检索正确打印机驱动程序的位置、加载该驱动程序、将高级函数调用假脱机到打印作业、调度打印作业以进行打印,等等。后台处理程序在系统启动时加载并继续运行,直到操作系统关闭。打印后台处理程序的主要组件如下图所示。
应用
打印应用程序通过调用图形设备接口 (GDI) 函数或直接在winspool.drv
.
GDI
图形设备接口 (GDI) 包括用于图形支持的用户模式和内核模式组件。
胜池驱动程序
winspool.drv
是进入 Spooler 的客户端接口。它导出构成 Spooler 的 Win32 API 的函数,并提供用于访问服务器的 RPC 存根。
spoolsv.exe
spoolsv.exe
是 Spooler 的 API 服务器。它被实现为操作系统启动时启动的服务。此模块将 RPC 接口导出到 Spooler 的 Win32 API 的服务器端。spoolsv.exe
包括winspool.drv
(本地)和win32spl.dll
(远程)的客户。该模块实现了一些 API 函数,但大多数函数调用通过路由器 ( spoolss.dll
) 传递给打印提供者。
路由器
路由器,spoolss.dll
,根据每个函数调用提供的打印机名称或句柄确定调用哪个打印提供程序,并将函数调用传递给正确的提供程序。
打印提供商
支持指定打印设备的打印提供程序。
本地打印提供商为通过本地打印提供商的端口监视器访问的所有打印机提供作业控制和打印机管理功能。
下图提供了当应用程序创建打印作业时本地打印机提供商组件之间的控制流视图。 https://docs.microsoft.com/en-us/windows-hardware/drivers/print/introduction-to-print-providers
虽然这个控制流程相当大,但我们将主要关注本地打印提供商localspl.dll
。
该漏洞包括 CVE-2020–1030 的两次绕过。我强烈建议阅读Victor Mata 关于 CVE-2020–1030 的博客文章,但我也会尝试涵盖重要部分。
当用户打印文档时,打印作业被假脱机到称为“假脱机目录”的预定义位置。假脱机目录可在每台打印机上配置,并且它必须允许FILE_ADD_FILE
所有用户的权限。
默认假脱机目录的权限
SpoolDirectory
通过在打印机的注册表项中定义值来支持各个假脱机目录HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\<printer>
。
Print Spooler 提供用于管理配置数据的 API,例如EnumPrinterData
、GetPrinterData
、SetPrinterData
和DeletePrinterData
。在下面,这些函数执行与打印机键相关的注册表操作。
我们可以使用 修改打印机的配置SetPrinterDataEx
。此功能需要使用PRINTER_ACCESS_ADMINISTER
访问权限打开打印机。如果当前用户无权打开具有PRINTER_ACCESS_ADMINISTER
访问权限的现有打印机,则有两种选择:
默认情况下,INTERACTIVE
组中的用户具有“管理服务器”权限,因此可以创建新的本地打印机,如下所示。
Windows 桌面上本地打印服务器的安全性
但是,似乎此权限仅在 Windows 桌面上授予,例如 Windows 10 和 Windows 11。在我在 Windows 服务器上进行测试期间,此权限不存在。尽管如此,没有“管理服务器”权限的用户仍然可以添加远程打印机。
如果用户添加了远程打印机,该打印机将从打印机服务器继承共享打印机的安全属性。因此,如果远程打印机服务器允许Everyone
管理打印机,则可以获得具有PRINTER_ACCESS_ADMINISTER
访问权限的打印机句柄,并SetPrinterDataEx
照常更新本地注册表。因此,用户可以在不同的服务器或工作站上创建共享打印机,并授予Everyone
管理打印机的权限。在受害者服务器上,用户可以添加远程打印机,现在可以由Everyone
. 虽然我尚未完全测试此漏洞在远程打印机上的行为方式,但它可能对于用户无法创建或管理本地打印机的情况,这是一个可行的选择。但请注意,虽然某些操作由本地打印提供商处理,但其他操作由远程打印提供商处理。
当我们打开或创建了具有PRINTER_ACCESS_ADMINISTER
访问权限的打印机后,我们可以SpoolDirectory
对其进行配置。
调用时SetPrinterDataEx
,会向本地 Print Spooler 发送一个 RPC 请求spoolsv.exe
,后者会将请求路由到localspl.dll!SplSetPrinterDataEx
. 控制流由以下事件组成:
spoolsv.exe!SetPrinterDataEx
路线SplSetPrinterDataExlocalspl.dll
localspl.dll!SplSetPrinterDataEx
在恢复SYSTEM
上下文和修改注册表之前验证权限localspl.dll!SplRegSetValue
在设置SpoolDirectory
值的情况下,localspl.dll!SplSetPrinterDataEx
将在更新注册表项之前验证提供的目录是否有效。在 CVE-2020–1030 之前不存在此检查。
localspl.dll!SplSetPrinterDataEx
给定目录的路径,localspl.dll!IsValidSpoolDirectory
将调用localspl.dll!AdjustFileName
将路径转换为规范路径。例如,规范路径C:\spooldir\
是\\?\C:\spooldir\
,如果C:\spooldir\
是到 的符号链接C:\Windows\System32\
,规范路径是\\?\C:\Windows\System32\
。然后,localspl.dll!IsValidSpoolDirectory
将检查当前用户是否被允许打开或创建具有GENERIC_WRITE
访问权限的目录。如果目录已成功创建或打开,该函数将最终检查目录的链接数是否不大于 1,如GetFileInformationByHandle
.
localspl.dll!IsValidSpoolDirectory
因此,为了设置SpoolDirectory
,用户必须能够创建或打开具有可写权限的目录。如果验证成功,打印提供程序将更新打印机的SpoolDirectory
注册表项。但是,在重新初始化之前,后台打印程序不会创建后台打印目录。这意味着我们将需要弄清楚如何重新启动 Spooler 服务(我们将回到这部分),但这也意味着用户只需要能够在设置SpoolDirectory
注册表项时在验证期间创建目录 -而不是在实际创建目录时。
为了绕过验证,我们可以使用重解析点(在这种情况下为目录连接)。假设我们创建了一个名为 的目录C:\spooldir\
,并将其设置SpoolDirectory
为C:\spooldir\printers\
. 后台处理程序将检查用户是否可以printers
在C:\spooldir\
. 验证通过,SpoolDirectory
设置为C:\spooldir\printers\
. 在我们配置好之后SpoolDirectory
,我们可以转换C:\spooldir\
成一个指向的重解析点C:\Windows\System32\
。当 Spooler 初始化时,C:\Windows\System32\printers\
将创建每个人都具有可写权限的目录。如果该目录已经存在,Spooler 将不会对该文件夹设置可写权限。
因此,我们需要找到一个有趣的地方来创建一个目录。一个这样的地方是C:\Windows\System32\spool\drivers\x64\
,也称为打印机驱动程序目录(在其他体系结构上,它不是x64
)。打印机驱动程序目录特别有趣,因为如果我们SetPrinterDataEx
使用CopyFiles
注册表项调用,Spooler 将自动加载Module
值中分配的 DLL — 如果Module
文件路径允许的话。
以字符串pszKeyName
开头时触发此事件。CopyFiles\
它启动一系列函数,导致LoadLibrary
.
localspl.dll!SplSetPrinterDataEx
控制流由以下事件组成:
spoolsv.exe!SetPrinterDataEx
路线SplSetPrinterDataExlocalspl.dll
localspl.dll!SplSetPrinterDataEx
在恢复SYSTEM
上下文和修改注册表之前验证权限localspl.dll!SplRegSetValue
localspl.dll!SplCopyFileEvent
如果pszKeyName
参数以CopyFiles\
字符串开头,则调用localspl.dll!SplCopyFileEvent
中读取值并将字符串传递给ModuleCopyFileslocalspl.dll!SplLoadLibraryTheCopyFileModule
localspl.dll!SplLoadLibraryTheCopyFileModule
对Module
文件路径进行验证localspl.dll!SplLoadLibraryTheCopyFileModule
尝试加载模块LoadLibrary
验证步骤包括localspl.dll!MakeCanonicalPath
和localspl.dll!IsModuleFilePathAllowed
。localspl.dll!MakeCanonicalPath
如前所述,该函数将采用路径并将其转换为规范路径。
localspl.dll!MakeCanonicalPath
localspl.dll!IsModuleFilePathAllowed
将验证规范路径是否直接位于C:\Windows\System32\
打印机驱动程序目录内部或内部。例如,C:\Windows\System32\payload.dll
将被允许,而C:\Windows\System32\Tasks\payload.dll
不会。允许打印机驱动程序目录内的任何路径,例如C:\Windows\System32\spool\drivers\x64\my\path\to\payload.dll
允许。如果我们能够C:\Windows\System32\
在打印机驱动程序目录中或任何地方创建 DLL,我们可以将 DLL 加载到 Spooler 服务中。
localspl.dll!SplLoadLibraryTheCopyFileModule
现在,我们知道我们可以使用SpoolDirectory
为所有用户创建一个具有可写权限的任意目录,并且我们可以将任何 DLL 加载到驻留在C:\Windows\System32\
打印机驱动程序目录中的 Spooler 服务中。不过只有一个问题。如前所述,spool 目录是在 Spooler 初始化期间创建的。spool 目录是在localspl.dll!SplCreateSpooler
调用时创建的localspl.dll!BuildPrinterInfo
。在localspl.dll!BuildPrinterInfo
允许权限之前,Everybody
会FILE_ADD_FILE
进行最后检查以确保目录路径不在打印机驱动程序目录中。
localspl.dll!BuildPrinterInfo
这意味着后台处理程序初始化期间的安全检查会验证该SpoolDirectory
值不指向打印机驱动程序目录内部。如果是这样,Spooler 将不会创建 spool 目录,而只是回退到默认的 spool 目录。此安全检查也在 CVE-2020-1030 的补丁中实施。
总而言之,为了加载 DLL localspl.dll!SplLoadLibraryTheCopyFileModule
,DLL 必须驻留在打印机驱动程序目录中或直接位于C:\Windows\System32\
. 要在 Spooler 初始化期间创建可写目录,该目录不得 位于打印机驱动程序目录中。两者localspl.dll!SplLoadLibraryTheCopyFileModule
并localspl.dll!BuildPrinterInfo
检查路径是否指向打印机驱动程序目录内。在第一种情况下,我们必须确保 DLL 路径以 开头C:\Windows\System32\spool\drivers\x64\
,在第二种情况下,我们必须确保目录路径不以 开头C:\Windows\System32\spool\drivers\x64\
。
在这两次检查期间, 都SpoolDirectory
被转换为规范路径,因此即使我们将 设置SpoolDirectory
为C:\spooldir\printers\
然后转换C:\spooldir\
为指向 的重解析点,C:\Windows\System32\spool\drivers\x64\
规范路径仍然会变为\\?\C:\Windows\System32\spool\drivers\x64\printers\
。检查是通过剥离规范路径的前四个字节来完成的,即\\?\C:\Windows\System32\spool\drivers\x64\ printers\
成为C:\Windows\System32\spool\drivers\x64\ printers\
,然后检查路径是否以打印机驱动程序目录开头C:\Windows\System32\spool\drivers\x64\
。因此,第二个通过这两项检查的错误出现了。
如果我们将 spooler 目录设置为 UNC 路径,例如\\localhost\C$\spooldir\printers\
(并且C:\spooldir\
是指向 的重解析点C:\Windows\System32\spool\drivers\x64\
),则规范路径将变为\\?\UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\
,并且在比较过程中,前四个字节被剥离,因此UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\
被比较C:\Windows\System32\spool\drivers\x64\
并且将不再匹配。当 Spooler 初始化时,\\?\UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\
将创建具有可写权限的目录。我们现在可以将我们的 DLL 写入C:\Windows\System32\spool\drivers\x64\printers\payload.dll
. 然后我们可以触发localspl.dll!SplLoadLibraryTheCopyFileModule
,但这一次,我们可以正常指定路径为C:\Windows\System32\spool\drivers\x64\printers\payload.dll
。
我们现在有了在打印机驱动程序目录中创建可写目录并将驱动程序目录中的 DLL 加载到 Spooler 服务中的原语。剩下的唯一事情是重新启动 Spooler 服务,以便创建目录。我们可以等待服务器重新启动,但有一种技术可以终止服务并依靠恢复来重新启动它。默认情况下,Spooler 服务将在前两次“崩溃”时重新启动,但不会在后续失败时重新启动。
要终止服务,我们可以使用localspl.dll!SplLoadLibraryTheCopyFileModule
加载C:\Windows\System32\AppVTerminator.dll
。当加载到 Spooler 中时,库调用TerminateProcess
随后终止该spoolsv.exe
进程。此事件触发服务控制管理器中的恢复机制,进而启动新的后台处理程序进程。Accenture 的 Victor Mata 针对 CVE-2020-1030 解释了这种技术。
这是一个完整的实际利用。本例中使用的 DLL 将创建一个名为“admin”的新本地管理员。DLL 也可以在漏洞利用存储库中找到。 SpoolFool 在行动
漏洞利用的步骤如下:
AppVTerminator.dll
通过加载到 Spooler中强制 Spooler 重新启动以创建目录请记住,只需创建一次驱动程序目录即可加载所需数量的 DLL。无需多次触发漏洞利用,这样做很可能最终会无限期地终止 Spooler 服务,直到重新启动将其恢复。创建驱动程序目录后,可以继续从该目录写入和加载 DLL,而无需重新启动 Spooler 服务。在本文末尾可以找到的漏洞利用将检查驱动程序目录是否已经存在,如果存在,漏洞利用将跳过目录的创建并直接跳转到写入和加载 DLL。漏洞利用的第二次运行如下所示。 SpoolFool 的第二次运行
可以在此处找到功能漏洞利用和 DLL:https ://github.com/ly4k/SpoolFool 。
而已。微软已正式发布补丁。当我最初发现在实际创建目录的过程中也有检查时,我开始寻找其他有趣的地方来创建目录。我发现了Secret Club的Jonas L的这篇文章,其中 Windows 错误报告服务 (WER) 被滥用以利用任意目录创建原语。但是,该技术似乎无法在我的 Windows 10 机器上可靠运行。然而,这是非常可靠的,但假设用户可以管理打印机,这已经是这个漏洞的情况。SplLoadLibraryTheCopyFileModule
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。