作者:天融信阿爾法實驗室
原文鏈接:https://mp.weixin.qq.com/s/8qDQUu6FNpNjX1tY1KrAoQ

近日,有安全研究員在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.一個普通權限的域賬戶,用另一臺計算機使用該域賬戶登錄加入域環境。其中域賬戶權限如下

image-20210708152517603

2.域控主機需要能夠訪問到使用上述配置登錄的計算機的一個共享目錄,在Windows下可以使用smb實現,用管理員權限的powershell運行以下命令即可

mkdir C:\share
icacls C:\share\ /T /grant Anonymous` logon:r
icacls C:\share\ /T /grant Everyone:r
New-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 /f
REG ADD "HKLM\System\CurrentControlSet\Services\LanManServer\Parameters" /v NullSessionShares /t REG_MULTI_SZ /d share /f
REG ADD "HKLM\System\CurrentControlSet\Control\Lsa" /v EveryoneIncludesAnonymous /t REG_DWORD /d 1 /f
REG 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 impacket
git clone https://github.com/cube0x0/impacket
cd impacket
python3 ./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截圖:

reproduce

c++版本exp命令及RCE截圖:image-20210708154509121

漏洞分析

漏洞根原

漏洞的關鍵在于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。調試現場如下

image-20210708164408188

由于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 0000001f`7f83e938 00007ffc`fb225852 localspl!SplAddPrinterDriverEx
01 0000001f`7f83e940 00007ff6`6c23ba9f localspl!LocalAddPrinterDriverEx+0xa2
02 0000001f`7f83e990 00007ff6`6c215ffe spoolsv!AddPrinterDriverExW+0x6f
03 0000001f`7f83e9d0 00007ff6`6c212c71 spoolsv!YAddPrinterDriverEx+0x2ce
04 0000001f`7f83ea10 00007ffd`027184a3 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)

image-20210709083243361

隨后由于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,翻譯過來即強制加載驅動。

image-20210709090019330

CreateInternalDriverFileArray

localspl!CreateInternalDriverFileArray中會使用如下代碼根據RpcAddPrinterDriverEx 的dwFileCopyFlags參數生成CreateFile的參數,a5=1會使用%spooler%目錄下路徑做為CreateFile的參數;RCE利用時我們上傳的驅動此時是在一個UNC路徑下,如筆者本地為\\192.168.18.153\share\rev.dll ,所以這里需要構造dwFileCopyFlags&0x10=1使spooler使用我們的UNC路徑。

image-20210709090932362

其中a5參數從localspl!LocalAddPrinterDriverEx這里傳入,

image-20210709091622421

SplIsCompatibleDriver

localspl!SplIsCompatibleDriver會檢查將要加載的驅動的版本號,版本號v117只能為3

image-20210709091810684

其中v117會在localspl!InternalAddPrinterDriverEx這里校驗兩次,v117==2和v117>3都會導致驅動加載失敗。

image-20210709091925228

localspl!SplIsCompatibleDriver檢查驅動兼容性時會調用到ntprint!PSetupIsCompatibleDriver,最終會調用到如下代碼,其中a6=v117為驅動版本號,當v117<=2時返回0會導致驅動加載失敗。

image-20210709092214431

綜上,當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里。

New_to_Old

最終在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

參考

https://github.com/cube0x0/CVE-2021-1675

https://github.com/afwu/PrintNightmare


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1632/