写点什么

Windows Print Spooler 服务最新漏洞 CVE-2021-34527 详细分析

用户头像
H
关注
发布于: 3 小时前
Windows Print Spooler服务最新漏洞CVE-2021-34527详细分析

近日,有安全研究员在 github 上公开了”CVE-2021-1675”的 exp PrintNightmare,后经验证公开的 exp 是一个与 CVE-2021-1675 不同的漏洞,微软为其分配了新的编号 CVE-2021-34527。这篇文章记录了 CVE-2021-34527 的复现过程,并对漏洞成因进行了简单的分析。

漏洞复现


​ 这里记录域控环境下使用普通权限域账户实现 RCE 反弹 nt authority\system shell 的过程。下面的漏洞复现和漏洞分析都是基于 Windows server 2019,2021-6 补丁的,winver=17763.1999。经笔者测试在无任何补丁的 Windows server 2019,winver=17763.107 环境下使用以下步骤也可以复现 RCE。


环境配置


实现 RCE 的条件如下:


1.一个普通权限的域账户,用另一台计算机使用该域账户登录加入域环境。其中域账户权限如下


2.域控主机需要能够访问到使用上述配置登录的计算机的一个共享目录,在 Windows 下可以使用 smb 实现,用管理员权限的 powershell 运行以下命令即可

mkdir C:\shareicacls C:\share\ /T /grant Anonymous` logon:ricacls C:\share\ /T /grant Everyone:rNew-SmbShare -Path C:\share -Name share -ReadAccess 'ANONYMOUS LOGON','Everyone'REG ADD "HKLM\System\CurrentControlSet\Services\LanManServer\Parameters" /v NullSessionPipes /t REG_MULTI_SZ /d srvsvc /fREG ADD "HKLM\System\CurrentControlSet\Services\LanManServer\Parameters" /v NullSessionShares /t REG_MULTI_SZ /d share /fREG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v EveryoneIncludesAnonymous /t REG_DWORD /d 1 /fREG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v RestrictAnonymous /t REG_DWORD /d 0 /f
复制代码


运行完命令重启生效。


复现​


GitHub 上有 2 个公开的 exp,python 版本的https://github.com/cube0x0/CVE-2021-1675 和 C++版本的https://github.com/afwu/PrintNightmare ,其中 C++版本的是从 Zhiniang Peng (@edwardzpeng) & Xuefeng Li (@lxf02942370)公开的 exp fork 来的。


​ 这两个版本的 exp 原理都是一样的,也都是可用的,其中 python 版本的 exp 需要按照说明文档安装 exp 作者的 impacket 库,其余不需要修改任何东西。

pip3 uninstall impacketgit clone https://github.com/cube0x0/impacketcd impacketpython3 ./setup.py install​ 
复制代码


c++版本的 exp 需要把第 112 行 UNIDRV.DLL 的路径修改为域控主机对应的路径,如笔者这里对应的路径应修改为:


//info.pDriverPath = (LPWSTR)L"C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_19a3fe50fa9a21b6\\Amd64\\UNIDRV.DLL";info.pDriverPath = (LPWSTR)L"C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_83aa9aebf5dffc96\\Amd64\\UNIDRV.DLL";
复制代码


其余不需要修改任何东西,使用 vs 编译即可。


​ python 版本 exp 命令及 RCE 截图:


​ c++版本 exp 命令及 RCE 截图:


漏洞分析漏洞根源​ 漏洞的关键在于 localspl!SplAddPrinterDriverEx 中调用 InternalAddPrinterDriverEx 加载驱动前的验证 ValidateObjectAccess 是可以被跳过的。如下 localspl!SplAddPrinterDriverEx 中的汇编代码为存在漏洞可以导致 ValidateObjectAccess 被绕过的代码。


.text:0000000180085F25 loc_180085F25:                  ; CODE XREF: SplAddPrinterDriverEx+3F↑j.text:0000000180085F25                 bt      esi, 0Fh        ; esi=dwFileCopyFlags.text:0000000180085F29                 mov     ebx, 0.text:0000000180085F2E                 cmovnb  ebx, [rsp+58h+arg_30] ; [rsp+0x90]=1.text:0000000180085F2E                                         ; CF=1,不进行移位.text:0000000180085F36                 test    ebx, ebx.text:0000000180085F38                 jz      short loc_180085F64.text:0000000180085F3A                 mov     rax, cs:pLocalIniSpooler.text:0000000180085F41                 xor     r9d, r9d.text:0000000180085F44                 and     [rsp+58h+var_30], 0.text:0000000180085F49                 xor     r8d, r8d.text:0000000180085F4C                 xor     ecx, ecx.text:0000000180085F4E                 mov     [rsp+58h+var_38], rax.text:0000000180085F53                 lea     edx, [r9+1].text:0000000180085F57                 call    ?ValidateObjectAccess@@YAHKKPEAXPEAKPEAU_INISPOOLER@@W4SERVER_MANAGEMENT_ACCESS_REQUEST@@@Z ; ValidateObjectAccess(ulong,ulong,void *,ulong *,_INISPOOLER *,SERVER_MANAGEMENT_ACCESS_REQUEST)...
.text:0000000180085F64 loc_180085F64: ; CODE XREF: SplAddPrinterDriverEx+98↑j.text:0000000180085F64 ; SplAddPrinterDriverEx+BE↑j.text:0000000180085F64 and [rsp+58h+var_20], 0.text:0000000180085F6A mov r9d, esi.text:0000000180085F6D mov eax, [rsp+58h+arg_28].text:0000000180085F74 mov r8, r14.text:0000000180085F77 mov [rsp+58h+var_28], ebx.text:0000000180085F7B mov edx, r15d.text:0000000180085F7E mov [rsp+58h+var_30], eax.text:0000000180085F82 mov rcx, rdi.text:0000000180085F85 mov [rsp+58h+var_38], rbp.text:0000000180085F8A call InternalAddPrinterDriverEx
复制代码

其中 esi 为 dwFileCopyFlags,是一个调用者可控的参数,bt esi,0xf 将 esi 中偏移 0xf 的比特位保存到 CF 标志位,即 CF 标志位与 esi 的 0x10 比特位相同,dwFileCopyFlags=0x8014 时 CF=1。cmovnb ebx, [rsp+58h+arg_30] 即 mov if not below,cmovnb 会检测 CF 标志位是否为 0 且当 CF 为 0 时进行移位操作,此时[rsp+0x90]=1,CF=1 不会将 ebx 赋值为 1。调试现场如下


由于 ebx=0,jz short loc_180085F64 会跳转到 InternalAddPrinterDriverEx 处执行后续复制并加载驱动的操作,跳过了 0x180085F57 处 ValidateObjectAccess 的检测。


InternalAddPrinterDriverEx​


RpcAddPrinterDriverEx 会在 spoolsv!RpcAddPrinterDriverEx 处解析,调用到 localspl!LocalAddPrinterDriverEx 处的回调,并最终由于 localspl!SplAddPrinterDriverEx 处的验证 ValidateObjectAccess 无效导致可以调用到 localspl!InternalAddPrinterDriverEx 加载驱动并执行。


​ 调用到 localspl!SplAddPrinterDriverEx 时的栈回溯如下


0:009> k
Child-SP RetAddr Call Site
00 0000001f7f83e938 00007ffcfb225852 localspl!SplAddPrinterDriverEx01 0000001f7f83e940 00007ff66c23ba9f localspl!LocalAddPrinterDriverEx+0xa202 0000001f7f83e990 00007ff66c215ffe spoolsv!AddPrinterDriverExW+0x6f03 0000001f7f83e9d0 00007ff66c212c71 spoolsv!YAddPrinterDriverEx+0x2ce04 0000001f7f83ea10 00007ffd027184a3 spoolsv!RpcAddPrinterDriverEx+0x181...​
复制代码

2021-6 的补丁中在 spoolsv!RpcAddPrinterDriverEx 中调用 YAddPrinterDriverEx 加载驱动前加了几处校验,如下右为补丁后的 spoolsv.exe。补丁后 YIsElevated、RunningAsLUA 分别校验了当前用户的 token 和 LUA 权限,这两处校验在 RCE 中可以通过 IPC 被绕过;YIsElevationRequired 检验了 HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows NT\Printers\PointAndPrint\NoWarningNoElevationOnInstall 的注册表项,但是笔者在 2021-6 全补丁的 Windows server 和 Windows10 系统上均未发现有该注册表项,所以这个缓解在目前来看也是无效的。(这两处缓解可能是针对 Yunhai Zhang 和 ZhiPeng Huo 提供的 CVE-2021-1675 的 poc)


​ 随后由于 spoolsv!AddPrinterDriverExW 调用到 localspl!LocalAddPrinterDriverEx 处的回调,又由于上述分析的 localspl!SplAddPrinterDriverEx 中验证无效进入 localspl!InternalAddPrinterDriverEx 的流程。


​ localspl!InternalAddPrinterDriverEx 主要进行了如下操作,其中 %spooler%=C:\Windows\System32\spool\


1.ValidateDriverInfo进行驱动签名等的检查
2.CreateInternalDriverFileArray创建spooler目录下的驱动文件,即%spooler%\drivers\x64
3.GetPrintDriverVersion、CheckFilePlatform检查驱动版本和驱动运行平台
4.SplIsCompatibleDriver进行驱动版本和驱动兼容性检查,驱动版本号只能为3
5.CreateVersionDirectory使用提供的驱动版本号,创建spooler目录下驱动版本号目录,由于驱动版本号只能为3,最终目录为%spooler%\drivers\x64\3
6.CopyFilesToFinalDirectory创建%spooler%\3目录下New、Old文件夹,创建New、Old目录下的临时目录,如%spooler%\drivers\x64\3\Old\1、%spooler%\drivers\x64\3\Old\2;并将上传的驱动移动到临时目录下
7.WaitRequiredForDriverUnload加载6中临时目录下的驱动,路径如%spooler%\drivers\x64\3\old\1\xx.dll
复制代码

ValidateDriverInfo


​ localspl!ValidateDriverInfo 在如下代码会校验加载驱动的签名,可以使用 0x8000 的 dwFileCopyFlags 绕过,0x8000 即 RpcAddPrinterDriverEx 的 API 文档中提到的 APD_INSTALL_WARNED_DRIVER,翻译过来即强制加载驱动。


CreateInternalDriverFileArray


​ localspl!CreateInternalDriverFileArray 中会使用如下代码根据 RpcAddPrinterDriverEx 的 dwFileCopyFlags 参数生成 CreateFile 的参数,a5=1 会使用 %spooler%目录下路径做为 CreateFile 的参数;RCE 利用时我们上传的驱动此时是在一个 UNC 路径下,如笔者本地为\192.168.18.153\share\rev.dll ,所以这里需要构造 dwFileCopyFlags&0x10=1 使 spooler 使用我们的 UNC 路径。


其中 a5 参数从 localspl!LocalAddPrinterDriverEx 这里传入,


SplIsCompatibleDriver


​ localspl!SplIsCompatibleDriver 会检查将要加载的驱动的版本号,版本号 v117 只能为 3


其中 v117 会在 localspl!InternalAddPrinterDriverEx 这里校验两次,v117==2 和 v117>3 都会导致驱动加载失败。


​ localspl!SplIsCompatibleDriver 检查驱动兼容性时会调用到 ntprint!PSetupIsCompatibleDriver,最终会调用到如下代码,其中 a6=v117 为驱动版本号,当 v117<=2 时返回 0 会导致驱动加载失败。


综上,当 v117==2、v117>3、v117<=2 时均会最终导致驱动加载失败,v117 只能为 3。


CopyFilesToFinalDirectory


​ localspl!CopyFilesToFinalDirectory 主要是创建 %spooler%\drivers\x64\3\New、%spooler%\drivers\x64\3\Old,并创建临时目录如


%spooler%\drivers\x64\3\Old\1,将 UNIDRV.DLL、kernelbase.dll、rev.dll 依次从 C:\Windows\System32\spool\drivers\x64\3\New、C:\Windows\System32\spool\drivers\x64\3 里使用 MoveFileExW 移动到 %spooler%\drivers\x64\3\Old\1 里。


最终在 localspl!CompleteDriverUpgrade 里更新所加载驱动的信息并加载上述临时目录下的驱动。


总结​ 据 Zhiniang Peng (@edwardzpeng) & Xuefeng Li (@lxf02942370)在最初公开的 exp README 里描述,spooler 的漏洞最初用于 10 年前的震网(Stuxnet)攻击,10 年间 spooler 模块也被披露了许多漏洞,但不知是因为微软补丁修复的不彻底还是 spooler 模块本身实现起来的复杂性导致了 CVE-2021-1675 和 CVE-2021-34527 的出现。微软已于 2021.7.7 发布了一个紧急安全更新补丁,希望微软的这个补丁能使 spooler 更安全一些吧;p


【兄弟们,跟我一起来 rua 它】


用户头像

H

关注

还未添加个人签名 2021.07.04 加入

想白嫖网安学习资料的,扣我

评论

发布
暂无评论
Windows Print Spooler服务最新漏洞CVE-2021-34527详细分析