From CVE-2020-1048 to CVE-2020-17001

2020年11月11日的每月微软补丁日修复了一个打印机的任意写漏洞CVE-2020-17001,该漏洞由Google project zero在当天披露了相关细节,有意思的是这个漏洞实际上是之前CVE-2020-1337未修复完全所导致,而CVE-2020-1337 同样又是另一个漏洞CVE-2020-1048未完全修复而来,CVE-2020-1048是一个打印机服务导致的任意文件写漏洞,而该漏洞由于微软在修补上的一再失误致使接连两次补丁都失效,这样的情况在微软的修复史上也是比较少见了,类似的情况我有印象的是2018年系列的vbs漏洞修复,详情可以见我当时的文章(https://www.cnblogs.com/goabout2/p/10259308.html),这里我们来详细看看这三个漏洞及对应的修复过程,并以此来看看应该如何去很好的修复一个漏洞。

首先是最初的CVE-2020-1048,该漏洞很简单,就是在创建了一个打印机驱动,打印机port之后,通过该打印机驱动及port生成的打印机,可以直接往port指定的路径写入任意文件,但是这里有一定限制,即需要exp运行之后重启打印机服务或系统,正常情况下打印机进程直接运行的时候是有对应的权限模拟的,即不能直接写入到类似system32这样的高权限文件夹,因此运行exp后会报错,但是当打印机出错之后,对应的打印机进程并不会退出,其会将任务挂起,其本意是针对类似远程打印时网络突然断掉/或打印机服务崩溃这种情况,并将相关打印任务的信息保存到SHD/SPL文件中,这样一旦网络恢复或者打印机服务重启,对应的任务能继续下去,但是如果打印机服务重启之后,相关的任务信息都来自于SHD/SPL文件,SHD/SPL文件中却并不会记录之前进程的相关权限信息,因此之前挂起的恶意任务将继续恢复执行,且此时是system的权限。

Add-PrinterDriver -Name "Generic / Text Only"

Add-PrinterPort -Name " C:WindowsSystem32myport.txt "

Add-Printer -Name "PrinterDemon" -DriverName "Generic / Text Only" -PortName " C:WindowsSystem32myport.txt "

Get-Printer | ft Name,DriverName,PortName

相关代码poc引用自参考研究报告,仅用于技术分享,一切非法攻击后果与作者无关

而微软针对该漏洞的修复就比较简单了,直接在port创建函数LcmCreatePortEntry中新增了函数IsValidNamedPipeOrCustomPort和PortIsValid。

 

 其中IsValidNamedPipeOrCustomPort检测该port是否为命名管道,且是否可以通过createfile打开,如果不是命名管道,则会检测port中是否包含和/字符。

而PortIsValid则判断这个port是否合法,主要是检测是否为com/lpt port,并尝试通过创建对应的port文件句柄来判断是否拥有读写权限。

但是实际上上面的修复方式是有问题的,即只是简单的检测了port的创建过程,而创建过程中,IsValidNamedPipeOrCustomPort和PortIsValid只要有一个不返回0即可以通过判断。理论上正常情况下无论如何构造都无法通过IsValidNamedPipeOrCustomPort的判断,因为路径一定会包含/,但是我们只要绕过PortIsValid的判断即可,PortIsValid只需要判断我们的port路径是否具有可读写权限。如果我们一开始port生成的时候指向一个有读写权限的路径如下poc中的C: est est.dll,在创建打印机之后,删除该目录,并通过目录链接,将删除的目录和system32目录链接,这样将导致我们对test目录的操作实际转到对system32的操作中,从而达到了port创建的时候为正常权限目录,而重启之后实际打印的链接却指向了system目录,这就是CVE-2020-1337.

mkdir "C:	est"

Add-PrinterDriver -Name "Generic / Text Only"

Add-PrinterPort -Name "C:	est	est.dll"

Add-Printer -Name "PrinterExploit" -DriverName "Generic / Text Only" -PortName "C:	est	est.dll"

rmdir "c:	est"

New-Item -ItemType Junction -Path "C:	est" -Value "C:windowssystem32"

相关代码poc引用自参考研究报告,仅用于技术分享,一切非法攻击后果与作者无关

可以看到直接绕过PortIsValid的检测返回1,并在之后通过文件链接写入到system目录。

 

  而此时微软发现了并不能单一的在打印机port创建的位置进行检测,此时针对该漏洞在函数LcmStartDocPort中进行了处理,LcmStartDocPort如下所示,其实际上就是用于开始打印工作的函数,在重启打印后会调用,用以完成之前的任务,可以看到LcmStartDocPort增加的代码首先是IsValidNamedPipeOrCustomPort,之后是IsPortANetworkPrinter,其中IsValidNamedPipeOrCustomPort是在CVE-2020-1048的补丁中引入,其能有效的识别非法的port,IsPortANetworkPrinter则是该次补丁中增加的函数,用于识别是否是一个网络打印机,之后一旦任意函数检验不通过,则通过IsPortAlink检测其是否为一个链接,如果是,直接删除对应的文件。

 而这次修补的问题在于IsPortAlink函数本身的局限性,IsPortAlink中,如果我们的port路径直接使用一个UNC路径\localhostc$ est estdll时,在IsPortAlink的检测中,GetFinalPathNameByHandleW针对该路径返回的真实路径为\?UNClocalhostc$ est estdll,这里可以看到GetFinalPathNameByHandleW此时返回的真实路径只是简单的在其前面增加了\?UNC的前缀,并不会将其真正对应的link链接返回。由于增加了\?前缀,导致之后wcsnicmp比较成功,调用函数ConvertFullPathToLongUNC,该函数会尝试将port路径转化为UNC路径,最终的结果同样为\?UNClocalhostc$ est estdll,这就导致最终第三个红框中的判断相等,从而不会进入1337 poc运行时触发的检测流程,最终IsPortAlink返回0,而这就是第三个漏洞CVE-2020-17001.

   如下所示可以看到只需要简单的修改CVE-2020-1337中的poc,将port的地址换为\localhostc$ est est.dll即可。

mkdir "C:	est"

Add-PrinterDriver -Name "Generic / Text Only"

Add-PrinterPort -Name "\localhostc$	est	est.dll"

Add-Printer -Name "PrinterExploit" -DriverName "Generic / Text Only" -PortName "\localhostc$	est	est.dll"

rmdir "c:	est"

New-Item -ItemType Junction -Path "C:	est" -Value "C:windowssystem32"

相关代码poc引用自参考研究报告,仅用于技术分享,一切非法攻击后果与作者无关

此时直接可以绕过IsPortAlink的检测。

  为了修复CVE-2020-17001,微软直接在LcmStartDocPort中增加了函数IsSpoolerImpersonating,该函数用于检测当前进程是否运行在system权限,如果是则直接进入到检测逻辑中,删除该port对应的文件,这里的修补相当于给重启后的打印机进程增加了一层权限模拟,以防止任意文件写入。

 

 IsSpoolerImpersonating的system检测代码。

 

 如下所示为整个该系列漏洞修复过程中微软的修改,针对CVE-2020-1048只是简单的在打印机port创建的时候判断该port指向路径的合法性及对应目录的权限是否正确,而忽视了实际重启后port的状态,因此通过目录链接可以直接绕过该检测,这就是CVE-2020-1337,而CVE-2020-1337的修复中在重启后的实际打印任务函数LcmStartDocPort中增加了函数IsPortAlink以实现对目录链接的检测,同时该函数还增加到了打印机port创建函数LcmCreatePortEntry(在其子函数PortIsValid)中,这看似万无一失了,但是由于IsPortAlink的局限性,其对于UNC链接的路径处理的时候不够严谨,直接导致了CVE-2020-17001。而此时微软也意识到了,以其一味的去检测攻击者不停尝试的各种打印机port,不如直接在LcmStartDocPort中增加函数IsSpoolerImpersonating以确保当时的进程权限正确才是一劳永逸的方式,而实际上大多数这种逻辑上导致的任意文件写漏洞也往往都是因为权限控制不当导致的。

 

转载请注明出处

参考链接:
https://github.com/shubham0d/CVE-2020-1048
https://github.com/neofito/CVE-2020-1337/tree/master/BinaryPlanting
https://bugs.chromium.org/p/project-zero/issues/detail?id=2075

原文地址:https://www.cnblogs.com/goabout2/p/13971925.html