CVE-2022-21999(CVE-2022–22718)是微软2月周二补丁所爆出来的打印机本地提权漏洞。其本质上是CVE-2020-1030的绕过。
POC:https://github.com/ly4k/SpoolFool
靶机: win10 x64 专业版,1909
主要组件
Print Spooler是打印后台处理服务,即管理所有本地和网络打印队列及控制所有打印工作。如果此服务被停用,本地计算机上的打印将不可用。主要组件如下图所示:
具体组件信息微软官方文件有详细阐述,感兴趣的同学可通过参考链接1进行查阅。
Spool directory
当用户打印文档时,打印作业被spool送到一个预定的位置,称为spool directory。另外spool directory在每台打印机设备上都是可以配置(修改)的,而且它会赋予所有用户拥有FILE_ADD_FILE权限。
通过在打印机的注册表项设置spool
directory的值来支持单个spool directory。
Point and Print
Point and Print 是为驱动程序分发而设计的多种打印机共享技术之一。在Point and Print中,驱动程序(v4 之前的型号)和配置文件会自动从打印服务器下载。安装可通过自定义的 Point and Print DLL 进行扩展。该DLL是通过在打印机配置中定义 CopyFiles 注册表项来实现的。
相关函数
Print spool提供以下四个函数用于管理配置数据
以上函数去执行与print`s Key(pValueName控制)相关的注册表操作。
当我们通过上述函数修改print的配置时,有一个大前提就是打印机需要以PRINTER_ACCESS_ADMINISTER权限被打开。
SetPrinterDataEx分析
带有 CopyFiles 注册表项的 SetPrinterDataEx 会导致spooler自动加载在 Module 值中分配的 Point and Print DLL。其调用链如下:
用户层调用SetPrinterDataEx函数,Winspool会向本地Print Spooler(LocalSpl)发送一个RPC请求,后者会将请求route 到SplSetPrinterDataEx 中的本地打印提供程序实现。
在localspl.dll!SplSetPrinterDataEx中会对pKeyName的值进行匹配
当 pszKeyName 以”CopyFiles\”字符串开头时触发Load事件,会调用SplCopyFileEvent去处理。
SplCopyFileEvent首先通过调用SplGetPrinterDataEx获取模块路径长度,为其申请相应的内存,接着再次通过SplGetPrinterDataEx获取模块完整路径。最后将要加载的模块路径传入SplLoadLibraryTheCopyFileModule。
调试信息如下:
SplLoadLibraryTheCopyFileModule会对模块路径进行验证,只有通过验证后才可以加载DLL。
MakeCanonicalPath将模块路径转换为规范路径。
IsModuleFilePathAllowed函数将验证模块规范路径是否在system路径或者打印机驱动目录下。
Tips:匹配算法是通过是将模块规范路径去掉其前四个字节后,进行匹配的。即?\C:\Windows\System32\spool\drivers\x64
printers\变为C:\Windows\System32\spool\drivers\x64
printers\。此种方法存在一定的缺陷,漏洞利用也是针对这种检查方式的绕过。
漏洞点分析
Spooler在加载Point and Print DLL时会依次搜索一下两个路径:
我们可以从第二个路径做文章,也就是打印机驱动程序目录,我们可以利用一个不存在的版本(4)驱动程序目录来进行利用。如果我们能够创建具有读写权限的目录,那么missing path可能存在代码执行机会。然后可以将任意 DLL 放置到文件路径中,并通过
SetPrinterDataEx 调用它。
创建打印机
为获取PRINTER_ACCESS_ADMINISTER权限,要在本地创建一台新的打印机。
设置Spool directory
通过SetPrinterDataEx设置Spool directory,获取一个拥有FILE_ADD_FILE的权限的文件目录。
这里需要特殊说明一下,在使用CVE-2020-1030漏洞补丁更新后,SetPrinterDataEx在设置Spool directory时会对其传入的路径进行一次检查。
IsValidSpoolDirectory将要设置的Spool directory路径转换为规范路径后,测试其是否具有GENERIC_WRITE权限。
验证成功后,在打印机注册表项对Spool directory进行更改。
符号链接
设置自定义Spool directory目的是我们想通过它去执行任意DLL,但在通过我们对SetPrinterDataEx可知,其在加载DLL前会对其路径进行一次检查(必须是system路径或打印机驱动目录),此时我就可以通过符号链接(重解析)来绕过。
即让自定义的路径[\localhost\C$/spooldir)指向C:\Windows\System32\spool\drivers\x64\,至于这里为什么要用UNC路径,后面会进行解释。
重新启动Spooler服务
在第2步中的设置Spool directory,并没有实际进行创建,仅是在注册表中进行设置。而实际创建目录仅在 Spooler初始化时才会被创建。但此时我们的打印机已经被创建,Spooler的初始化也已经完成。看起来很难完成,但车到山前必有路,可以通过将 AppVTerminator.dll 加载到 Spooler中,强制 Spooler重新启动以创建目录。
AppVTerminator的Dllmain可直接结束当前进程(spoolsv.exe)。
Spooler被强制结束后,会触发其恢复机制,从而达到重启服务的目的。
Spooler重启之后会通过localspl!SplCreateSpooler
调用 localspl!BuildPrinterInfo 时会创建spool directory。在 localspl!BuildPrinterInfo
赋予FILE_ADD_FILE 权限之前,进行最后检查以确保目录路径不在打印机驱动程序目录中。
Tips:此项路径检查同样是在CVE-2020-1030的补丁中添加。
综上所述,想要成功加载一个DLL我们必须保证以下两点:
BuildPrinterInfo检查时,保证Spool directory路径不以
这样看起来好像很矛盾,但它的检查算法(IsModuleFilePathAllowed)是存在缺陷的(去掉前4个字节匹配),因此我们可以将Spool directory设置为UNC路径,例如[\localhost\CKaTeX parse error: Undefined control sequence: \spooldir at position 1: \̲s̲p̲o̲o̲l̲d̲i̲r̲\printers\](fil…/spooldir/printers/),经过设置软连接后,它的规范路径是\?\UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\,因此去掉前四个字节后与C:\Windows\System32\spool\drivers\x64\也不匹配。成功过掉检查后,BuildPrinterInfo就会给Spool directory(C:\Windows\System32\spool\drivers\x64\)赋予FILE_ADD_FILE权限。
写入DLL并加载
Spooler服务成功重启后,我们可以将想要执行的DLL文件写入到C:\Windows\system32\spool\DRIVERS\x64\4,然后通过SplLoadLibraryTheCopyFileModule去加载(以SYSTEM权限加载)。
CVE-2022-21999实际上是CVE-2020-1030的绕过,其核心是UNC路径和符号链接。这里简单介绍一下符号链接。
符号链接是将自己链接到一个目标文件或目录的路径上。当系统识别到符号链接时,它会跳转到符号链接所指向的目标中去,而不改变此时的文件路径。实际上,我认为这就是高级快捷方式。
如下图所示,为打印机驱动程序目录创建符号链接\localhost\C$\Users\jiabin3\AppData\Local\Temp\c0e601f7-92b2-4167-b18f-3a23c65eaa94。然后可以看到,打开此路径之后
,其内部的目录(文件)结构与打印机驱动程序目录是一致的。唯一的区别是他们的路径不一样,我认为这是一种高级映射方式。
Print Spool组件介绍
https://docs.microsoft.com/en-us/windows-hardware/drivers/print/introduction-to-spooler-components
Windows Print Spooler Privilege
EscalationWindows 符号链接