作者:启明星辰ADLab
原文链接:https://mp.weixin.qq.com/s/1FyUakK_u_LUKHeSFu52Ew

一、漏洞概述

微软在5月12日的安全更新中公开了一个Windows本地提取漏洞(CVE-2020-1048),该漏洞的描述为:

Windows Print Spooler服务不恰当地允许任意的文件系统写入,存在特权提升漏洞。攻击者利用此漏洞能够用系统特权运行任意代码,从而实现:程序的安装、查看、更改或数据删除,以及创建具有完整权限的帐户。要利用此漏洞,攻击者必须登录到受影响的系统并运行特定脚本或应用程序”。

该漏洞由安全研究人员Alex Ionescu和Yarden Shafir发现,并被命名为PrintDemon。Print Spooler是系统自带的打印后台处理服务,管理所有本地和网络打印队列,控制着所有打印工作。Print Spooler在Windows系统中已存在多年,从微软发布的补丁页面可知该漏洞影响Windows7至Windows10 1909的几乎所有版本。

二、漏洞验证

启明星辰ADLab安全研究员对该漏洞进行了分析和验证,实现了在低权限的标准用户下写入系统目录,测试操作系统为Windows 10 x64企业版2016(长期服务版),测试步骤如下:

1)在测试系统中创建一个标准用户test,并使用该标准用户登录系统。查看其所属用户组,确认其不是管理员用户组。

2)在test账户下,尝试在系统目录下创建文件夹或者写入文件,均失败。

3)然后执行如下PowerShell命令,以期在系统目录下创建文件myport.txt。

4)重启测试系统并登录test用户,可以看到在系统目录下已生成了myport.txt文件,查看内容确实包含了测试字符串。该结果表明:低权限的 test用户突破了无法修改系统资源的安全限制。

三、漏洞原理

该漏洞涉及到Windows打印机的工作机制,为更好的理解漏洞成因,首先简单介绍打印机基础知识,然后再分析漏洞成因。

打印机工作机制

Windows系统的打印机有两个核心组件:打印机驱动和打印机端口。

  • 打印机驱动

在添加一个打印机时,需要安装打印机驱动。在MSDN文档描述中,早期系统要求只有具备SeLoadDriverPrivilege权限的用户才能安装打印驱动,但为了便于标准用户安装驱动,从Windows Vista开始,只要打印机驱动是已经存在的可立即使用的驱动,就不需要任何特权即可安装。例如,通过一条PowerShell命令即可安装“Generic / Text-Only”驱动。

  • 打印机端口

在添加一个打印机时,需要设置打印机的端口。Windows支持多种类型的打印机端口: LPT1端口、USB端口、网络端口和文件等。如果设置端口为文件,则意味着打印机将数据打印到指定文件。例如,通过一条PowerShell命令即可添加一个输出到指定文件的打印端口。

Add-PrinterPort -Name "C:\windows\Temp\myport.txt"

实际上,该操作是在注册表中增加一个REG_SZ类型的值。

准备好驱动和端口后,通过一条PowerShell命令即可创建一个打印机。

打印机创建完毕后,通过一条PowerShell命令即可打印数据到指定端口:

"Print Test!" | Out-Printer -Name "PrintTest"

由于PrintTest打印机的端口是文件c:\windows\Temp\myport.txt,因此打印命令执行后,数据“Print Test!”将会被写入(即打印)到该文件。

针对端口是文件的打印过程,spooler打印服务程序以impersonating方式来模拟当前用户的特权进行文件写入。因此,如果端口文件在受保护的系统目录(例如C:\Windows\system32), 则非管理员下的PowerShell打印作业就会失败。

脱机打印的机制

在Windows系统上,如果系统配置启用了假脱机服务,则所有的打印任务都不是立即执行。相反,系统使用Print Spooler来管理脱机打印任务。具体来说,当用户调用打印操作后,系统将打印作业存储在特定的假脱机文件夹中。

默认情况下,Windows生成的脱机打印任务文件为.SPL文件,此外Windows还会创建后缀名为.SHD的shadow文件并同SPL文件做关联。创建shadow文件的用途是:在打印程序出现问题或者打印任务被挂起后,Print Spooler依然可以通过SHD文件恢复打印任务。

在Windows系统重启或Print Spooler服务重启之后,.SHD和.SPL文件会被重新读取以恢复打印任务。

打印提权的原理

脱机打印机制使得Windows系统在重启后会恢复可能存在的未执行打印任务。但是,重启后的Printer Spooler服务程序直接使用了System权限来恢复未执行的打印作业。对于打印机端口为文件的打印任务,打印文件的写入也就在System权限下被执行。因此,系统重启使得脱机打印任务具备了System权限的任意文件写入能力。

打印机的设置除PowerShell脚本外,通过系统控制面板也能设置。具体来说,通过“设备和打印机”能添加打印机并设置端口。

但如果设置打印端口名为C:\Windows\system32\myport.txt,则会失败。

为何设置同样文件名的打印机端口,通过控制面板会失败,而通过PowerShell 命令则可以成功呢?通过分析这两种方式对spooler程序执行流程的影响,发现spooler程序对通过PowerShell命令行添加打印机端口方式缺乏安全校验。

具体来讲,针对PowerShell命令添加打印机端口,spooler程序直接设置了相应的打印机端口注册表项;针对控制面板添加打印机端口,spooler程序会首先尝试创建该端口文件,创建失败后就不会再设置相应的注册表项。

进一步分析相关API发现,Windows系统提供了两种添加打印机端口的API,分别是AddPort函数和XcvData函数。其中MSDN对AddPort的描述:

“AddPort函数浏览网络以查找现有端口,并弹出对话框供用户选择。 AddPort函数应该通过调用EnumPorts来验证用户输入的端口名称,以确保不存在重复的名称。AddPort函数的调用方必须具有访问端口所连接的服务器的SERVER_ACCESS_ADMINISTER权限。要添加端口而不显示对话框,可调用XcvData函数而不是AddPort ”。

通过控制面板添加打印机在底层是调用了AddPort函数,该函数会触发spooler程序对端口的合法性校验。通过PowerShell命令添加打印机在底层则是直接调用XcvData函数,该函数不会触发spooler程序对用户添加的端口进行安全校验。因此,测试程序AddPort.exe通过该函数在标准用户权限下也能设置打印机端口为受保护目录中的文件。

漏洞补丁的分析

分析漏洞修复后的版本发现,微软在关键函数LcmCreatePortEntry(最终创建打印机端口的函数)中添加了相应的端口合法性检查代码。下图是关键函数LcmCreatePortEntry在修复前和修复后的Call Graph对比,可以看出:补丁的核心是通过函数PortIsValid对端口进行合法性检查。

根据上文的分析可知,标准用户是无法在系统目录中创建文件的,把端口设置为系统目录下的文件会导致PortIsValid检测不到目标文件,从而判定要设置的端口是非法的。因此,在补丁修复后,标准用户添加打印端口为系统目录下文件的打印机就会始终失败,从而避免了系统重启时恢复恶意的打印服务。

四、修复建议

由于该漏洞能影响众多的Windows系统版本,而且可以在标准用户下发起漏洞攻击,建议受影响的用户及时进行系统更新或安装漏洞补丁。

此外,微软的安全更新只是对打印端口API进行了更严格的校验。但是,如果恶意文件端口在漏洞修复前已经创建,则漏洞攻击实际已经生效,此时进行系统更新仍然是不安全的。建议用户先使用PowerShell命令Get-PrinterPort来检查系统中是否存在可疑的打印机端口,在删除可疑端口后再实施系统更新。

参考资料

  1. https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1048

  2. https://docs.microsoft.com/en-us/windows/win32/printdocs/addport

  3. https://docs.microsoft.com/en-us/previous-versions/ff564255(v%3dvs.85)

  4. https://windows-internals.com/printdemon-cve-2020-1048/


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1218/