PrintNightmare

2021-12-10 17:30:22 浏览数 (1)

时间线

2021年6月29号:深信服安全研究员在Github上分布了名为《

PrintNightmare (CVE-2021-1675): Remote code execution in Windows Spooler Service》相关POC和漏洞信息

2021年6月30号:安全研究员@cube0x0在github上分布了使用Impacket的pyhton EXP。

2021年7月1号:安全研究员@cube0x0在github更新了C# Implementation of CVE-2021-1675的EXP。

2021年7月2号:微软披露Windows Print Spooler Remote Code Execution VulnerabilityCVE-2021-34527,表示已知该漏洞存在在野利用。该漏洞目前为零日状态,微软暂未发布修复。

...部分安全厂商把在github上披露的《PrintNightmare (CVE-2021-1675): Remote code execution in Windows Spooler Service》认为是新的CVE-2021-34527,也有部分厂商认为不是。

个人认为PrintNightmare (CVE-2021-1675): Remote code execution in Windows Spooler Service》为新的CVE-2021-34527,所以下面统一称PrintNightmare (CVE-2021-1675): Remote code execution in Windows Spooler Service》为新的CVE-2021-34527。。。。。

本人也是第一次分析漏洞,可能存在错误,希望大家多多包涵。

Print Spooler

Print Spooler是管理打印过程的可执行文件。打印管理涉及检索正确打印机驱动程序的位置、加载该驱动程序、将高级函数调用假脱机到打印作业中、安排打印作业进行打印等。后台处理程序在系统启动时加载并继续运行,直到操作系统关闭。

Print spooler 是一种管理打印过程的软件服务。后台处理程序接受来自计算机的打印作业并确保打印机资源可用。

任何经过身份验证的用户都可以远程连接到域控制器打印后台处理程序服务,并请求更新新的打印作业。

Print Spooler归 SYSTEM 所有

微软一般建议禁用:

代码语言:javascript复制
域控制器和 Active Directory 管理系统需要禁用打印后台处理程序服务。推荐的方法是使用组策略对象 (GPO)。

如果启用了Print Spooler服务,可以使用一些已知的 AD 凭据向域控制器的打印服务器请求新打印作业的更新,并告诉它向某个系统发送通知。当打印机将通知发送到任意系统时,它需要针对该系统进行身份验证。

因此,我们可以使Print Spooler服务针对任意系统进行身份验证,并且该服务将在此身份验证中使用计算机帐户。

RpcAddPrinterDriver

向服务器添加打印机驱动程序 (RpcAddPrinterDriver),RpcAddPrinterDriver可以在在打印服务器上安装打印机驱动程序并链接配置、数据和打印机驱动程序文件。

主要语法为:

代码语言:javascript复制
 DWORD RpcAddPrinterDriver(
   [in, string, unique] STRING_HANDLE pName,   
   [in] DRIVER_CONTAINER* pDriverContainer

pName

此参数是一个指向字符串的指针,该字符串指定该方法所操作的打印服务器的名称。这必须是远程过程调用 (RPC) 绑定到的域名系统 (DNS)、 NetBIOS、 互联网协议版本 4 (IPv4)、互联网协议版本 6 (IPv6)或通用命名约定 (UNC)名称,并且它必须唯一标识网络上的打印服务器。

pDriverContainer

该参数是指向 指定打印机驱动程序 信息的DRIVER_CONTAINER结构的指针。DRIVER_CONTAINER 结构的Level成员的值必须是 0x00000002、0x00000003、0x00000004、0x00000006 或 0x00000008。

返回值:

成功返回零 (ERROR_SUCCESS) ,失败返回非零 Windows 的错误代码

1.收到此消息后,服务器必须执行以下指定的验证步骤

  • 打印服务器名称参数。
  • DRIVER_CONTAINER 参数。

2.然后验证参数

验证该cVersion所述的构件DRIVER_INFO 结构包含在由指向DRIVER_CONTAINER pDriverContainer 比0x00000004严格以下。如果此验证失败,则返回 ERROR_PRINTER_DRIVER_BLOCKED。

验证pDriverContainer 参数 指向的 DRIVER_CONTAINER 中包含的DRIVER_INFO结构的pEnvironment成员不是“Windows ARM”。如果此验证失败,则返回 ERROR_NOT_SUPPORTED。

如果打印客户端请求的安装是打印机驱动程序升级,打印服务器应该执行以下额外的验证步骤:

  • 验证当前安装的打印机驱动程序不是类打印机驱动程序。
  • 验证如果当前安装的打印机驱动程序的驱动程序版本为 0x00000004,则当前安装的打印机驱动程序没有更新的驱动程序日期,或者如果驱动程序日期相同,则当前安装的打印机驱动程序没有更新的制造商 -提供的驱动程序版本号。
  • 验证如果当前安装的打印机驱动程序的驱动程序版本为 0x00000004,则打印服务器上没有打印机共享并且也使用当前安装的打印机驱动程序。

如果此验证失败,打印服务器必须返回 ERROR_PRINTER_DRIVER_BLOCKED。

3.如果参数验证失败

服务器必须立即使操作失败并向客户端返回一个非零错误响应。否则,服务器必须按如下方式处理消息并向客户端发送响应:

  • 将打印机驱动程序文件复制到目的地。如果复制操作失败,服务器必须立即使调用失败并向客户端返回一个非零错误响应。
  • 创建打印机驱动程序对象,使用特定于实现的机制来确定打印机驱动程序对象的每个属性的布尔值。<313>
  • 如果任何客户端注册了服务器对象更改的通知,则必须向它们广播通知。
  • 返回操作的状态。

添加或更新打印机驱动程序

要将打印机驱动程序 (“OEM 打印机驱动程序”)添加或更新到打印服务器 (“CORPSERV”),客户端(“TESTCLT”)执行以下步骤。

1.使用RpcEnumPrinterDrivers枚举现有的打印机驱动程序。

RpcEnumPrinterDrivers用来枚举安装在指定打印服务器上的打印机驱动程序。

2.如果打印机驱动程序不存在或客户端请求更新打印机驱动程序,那么我们可以使用RpcAddPrinterDriver 将驱动程序添加到打印服务器。

  • 客户端确保打印机驱动程序的文件位于服务器可访问的位置。所以,我们可以让客户端可以共享包含文件的本地目录,或使用SMB)协议将文件放入服务器上的目录中。
  • 然后客户端分配并填充一个DRIVER_INFO_2 结构

DRIVER_INFO_2 结构提供有关打印机驱动程序的信息

代码语言:javascript复制
pName = L"OEM 打印机驱动程序"; 
 pEnvironment = L"Windows NT x86"; /* 驱动程序兼容的环境 */ 
 pDriverPath = "\\CORPSERV\C$\DRIVERSTAGING\OEMDRV.DLL"; 
 pDataFile = "\\CORPSERV\C$\DRIVERSTAGING\OEMDATA.DLL"; 
 pConfigFile = "\\CORPSERV\C$\DRIVERSTAGING\OEMUI.DLL";
  • 客户端分配一个DRIVER_CONTAINER driverContainer 结构并初始化然后包含 DRIVER_INFO_2 结构。
代码语言:javascript复制
DRIVER_CONTAINER 结构通过使用DRIVER_INFO结构提供有关打印机驱动程序的信息。DriverInfo成员指定限定了打印机驱动程序的属性的结构。
  • 客户端调用 RpcAddPrinterDriver。
代码语言:javascript复制
 RpcAddPrinterDriver( L"\\CORPSERV", &driverContainer );
  • 服务器添加打印机驱动程序并返回 0(成功)。

CVE-2021-34527 分析

原文中是说通过绕过 RpcAddPrinterDriver 的身份验证。那么可以在打印服务器中安装恶意驱动程序来达到LPE 和 RCE。

在微软文档中我们可以知道RpcAddPrinterDriver中还会额外的验证

<311>验证为

Windows 服务器检查客户端用户是否具有 SERVER_ACCESS_ADMINISTER 权限。

<312>验证为:

The parameter validation performed by RpcAddPrinterDriver is not supported by Windows NT 3.1, Windows NT 3.5, Windows NT 3.51, Windows 95, Windows NT 4.0, Windows 98, Windows 2000, Windows Millennium Edition, Windows XP, Windows Server 2003, Windows Vista, Windows Server 2008, Windows 7, or Windows Server 2008 R2.

我们到知道上面整个添加或更新客户端和服务端的交互都是通过RPC来进行,其中我们需要加载安装驱动程序,那么一定需要客户端具有SeLoadDriverPrivilege权限。

那么我们想要在远程服务器添加安装驱动程序,那么第一步需要绕过SeLoadDriverPrivilege权限的检查。

按照原文的意思我们可以看一下当客户端需要调用RPC时。那么我们可以使用Process Monitor监视一下

Print Spooler服务在运行中的过程。

设置规则

可以检测spoolsv.exe的所有

通过阅读理解漏洞情况,我们可以大概可以定位到这个localspl.dll

通过查看这个DLL的相关信息我们可以知道:

Localspl.dll

Local print provider. Handles all print jobs directed to printers that are managed from the local server.

处理定向到从本地服务器管理的打印机的所有打印作业。

代码语言:javascript复制
https://docs.microsoft.com/en-us/windows-hardware/drivers/print/introduction-to-print-providers

同时也实现了打印提供程序定义的整套功能

代码语言:javascript复制
https://docs.microsoft.com/en-us/windows-hardware/drivers/print/local-print-provider

我们在查看这个整套功能中可以清楚看到:

代码语言:javascript复制
https://docs.microsoft.com/en-us/windows-hardware/drivers/print/functions-defined-by-print-providers

那么我们可以重点关注一下这个DLL

AddPrinterDriver

代码语言:javascript复制
添加打印机驱动程序 将指定打印机的驱动程序文件添加到指定服务器。 

在其它网站上我们可以查到

只有一个DLL会链接这个DLL,好吧。

重点看一下导出的函数列表

这里有个SplAddPrinterDriverEx ,听名字可以大概知道他的作用

直接提取c:windowssysytem32localspl_dll

使用IDA打开然后追踪SplAddPrinterDriverEx。

可以看到a4的值是可控的,ValidateObjectAccess 是 Spooler Service 的常规安全检查,那么普通用户可以绕过安全检查并添加驱动程序。

从微软文档中我们可以看到 print spooler 远程系统上引用或从远程系统复制和将打印机驱动程序或其他插件作为本地系统调用时的一些安全措施

代码语言:javascript复制
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-prsod/340e969b-3243-4116-bf79-47c45bb40264

Windows 实现可以执行以下一项或多项操作:

代码语言:javascript复制
限制非管理用户安装打印机驱动程序。
检查打印机驱动程序的数字签名。
在下载此类组件或首次执行该组件之前,提示用户同意。

上面的则可以绕过这些安全措施来使用非管理用户安装没有签名的打印机驱动程序

然后返回InternalAddPrinterDriverEx中

我们在漏洞原文中可以看到作者描述了文件复制的情况

同样我们在InternalAddPrinterDriverEx中也找到了相关文件复制操作

跟进一下CopyFilesToFinalDirectory

  • C:WindowsSystem32spooldriversx643
  • C:WindowsSystem32spooldriversx643old
  • C:WindowsSystem32spooldriversx643new

结合漏洞原文我们可以知道复制pDataFile ,pConfigFile ,pDriverPath之后的文件夹为:C:WindowsSystem32spooldriversx643new

然后再复制到 C:WindowsSystem32spooldriversx643中

并加载

C:WindowsSystem32spooldriversx643 [pDataFile]和 C:WindowsSystem32 spooldriversx643[pDriverPath] 进入 Spooler 服务中。

在Process中我们可以更为清晰地看到这一过程:

我们可以在

DRIVER_INFO_2 结构提供有关打印机驱动程序的信息

代码语言:javascript复制
pName = L"OEM 打印机驱动程序"; 
 pEnvironment = L"Windows NT x86"; /* 驱动程序兼容的环境 */  
 pDriverPath = "\\CORPSERV\C$\DRIVERSTAGING\OEMDRV.DLL";  
 pDataFile = "\\CORPSERV\C$\DRIVERSTAGING\OEMDATA.DLL";  
 pConfigFile = "\\CORPSERV\C$\DRIVERSTAGING\OEMUI.DLL";

这里定义好pDriverPath,pDataFile,pConfigFile这3个DLL的值/路径。

在Process中可以看到读取了DRIVER_INFO_2 结构中的3跟DLL

我们可以看一下堆栈,然后在ida中追一下,

按照漏洞原文的意思,这里我们可以重点关注一下这3个DLL的路径判断

代码语言:javascript复制
pDataFile =
pConfigFile =
pDriverPath=

这里不多描述。建议有兴趣的同学自己去找一下。

然后具体看一下复制文件的过程

需要使用驱动程序升级的备份功能,

旧版本将备份到 C:WindowsSystem32spooldriversx643old1 文件夹中。

最后就是加载我们的任意DLL进入 Spooler 服务中,这样就完成了漏洞利用。

如果在远程RCE中我们把pConfigFile 设置为UNC(Universal Naming Convention)地址就可以了。

注意:

代码语言:javascript复制
创建的smb服务允许匿名访问。
验证使用普通域用户的用户名和密码。
需要在域环境内。

理论上说影响所有运行有打印机服务的windows机器。

CVE-2021-34527 复现

目前公开的EXP主要有:

C

代码语言:javascript复制
https://github.com/hayasec/PrintNightmare

python/C#

代码语言:javascript复制
https://github.com/cube0x0/CVE-2021-1675

以及本地提权的

代码语言:javascript复制
https://github.com/hlldz/CVE-2021-1675-LPE

1.本地提权复现

使用的是

代码语言:javascript复制
https://github.com/hlldz/CVE-2021-1675-LPE

环境为:

复现不难,因为spoolsv.exe是x64的,所以我们这里使用Cobalt Strike的是x64的dll。

执行漏洞利用时,需要将 DLL 路径作为漏洞利用的第一个参数。就可以了!

代码语言:javascript复制
CVE-2021-1675-LPE.exe PAYLOAD_DLL_PATH

2.远程RCE复现

使用的是

代码语言:javascript复制
https://github.com/cube0x0/CVE-2021-1675

环境为:

攻击主机:WIN10 域普通用户 text 域内主机

攻击主机:windows server 2019 域控(DC)

按照https://github.com/cube0x0/CVE-2021-1675 的smb设置方法,在域内一台主机上提供匿名访问权限的共享文件

把恶意的DLL放进分享目录并允许匿名访问,在域控或目标主机上必须能直接获取到文件。

否则:

代码语言:javascript复制
报错 Error: code: 0x5 - rpc_s_access_denied 说明smb还不能匿名访问

这里使用的是C#版本的EXP来进行演示利用

在DC对应的文件夹中,我们可以看到

可以看到Cobalt Strike成功上线

防御方法

微软建议

确定 Print Spooler 服务是否正在运行

运行以下命令:

代码语言:javascript复制
Get-Service -Name Spooler

如果 Print Spooler 正在运行或该服务未设置为禁用,请选择以下选项之一以禁用 Print Spooler 服务,或通过组策略禁用入站远程打印:

选项 1 - 禁用 Print Spooler 服务

如果禁用 Print Spooler 服务适合您的企业,请使用以下 PowerShell 命令:

代码语言:javascript复制
Stop-Service -Name Spooler -Force
Set-Service -Name Spooler -StartupType Disabled

选项 2 - 通过组策略禁用入站远程打印

还可以通过组策略配置设置:

计算机配置/管理模板/打印机

禁用“允许打印后台处理程序接受客户端连接:”策略以阻止远程攻击。

限制 ACL

为驱动程序目录和所有子目录添加拒绝规则,防止 SYSTEM 帐户修改其内容。

代码语言:javascript复制
$Path = "C:WindowsSystem32spooldrivers"
$Acl = (Get-Item $Path).GetAccessControl('Access')
$Ar = New-Object  System.Security.AccessControl.FileSystemAccessRule("System", "Modify", "ContainerInherit, ObjectInherit", "None", "Deny")
$Acl.AddAccessRule($Ar)
Set-Acl $Path $Acl

检测方法

代码语言:javascript复制
EventID = '11' and Image like 'spoolsv.exe' and TargetFilename like 'C:WindowsSystem32spooldriversx643'
EventID      316
Message    INFO 316 NT AUTHORITYSYSTEM 已添加或更新 Windows x64 Version-3 的打印机驱动程序 1234。文件:- UNIDRV.DLL, kernelbase.dll, 123.dll。无需用户操作。

参考:

https://github.com/afwu/PrintNightmare

https://github.com/cube0x0/CVE-2021-1675

https://github.com/evilashz/CVE-2021-1675-LPE-EXP

0 人点赞