一、漏洞概述
微軟在5月12日的安全更新中公開了一個Windows本地提取漏洞(CVE-2020-1048),該漏洞的描述為:
“Windows Print Spooler服務不恰當地允許任意的檔案系統寫入,存在特權提升漏洞。攻擊者利用此漏洞能夠用系統特權執行任意程式碼,從而實現:程式的安裝、檢視、更改或資料刪除,以及建立具有完整許可權的帳戶。要利用此漏洞,攻擊者必須登入到受影響的系統並執行特定指令碼或應用程式”。
該漏洞由安全研究人員Alex Ionescu和Yarden Shafir發現,並被命名為PrintDemon。Print Spooler是系統自帶的列印後臺處理服務,管理所有本地和網路列印佇列,控制著所有列印工作。Print Spooler在Windows系統中已存在多年,從微軟釋出的補丁頁面可知該漏洞影響Windows7至Windows10 1909的幾乎所有版本。
二、漏洞驗證
啟明星辰ADLab安全研究員對該漏洞進行了分析和驗證,實現了在低許可權的標準使用者下寫入系統目錄,測試作業系統為Windows 10 x64企業版2016(長期服務版),測試步驟如下:
1)在測試系統中建立一個標準使用者test,並使用該標準使用者登入系統。檢視其所屬使用者組,確認其不是管理員使用者組。
2)在test賬戶下,嘗試在系統目錄下建立資料夾或者寫入檔案,均失敗。
3)然後執行如下PowerShell命令,以期在系統目錄下建立檔案myport.txt。
4)重啟測試系統並登入test使用者,可以看到在系統目錄下已生成了myport.txt檔案,檢視內容確實包含了測試字串。該結果表明:低許可權的 test使用者突破了無法修改系統資源的安全限制。
三、漏洞原理
該漏洞涉及到Windows印表機的工作機制,為更好的理解漏洞成因,首先簡單介紹印表機基礎知識,然後再分析漏洞成因。
印表機工作機制
Windows系統的印表機有兩個核心元件:印表機驅動和印表機埠。
在新增一個印表機時,需要安裝印表機驅動。在MSDN文件描述中,早期系統要求只有具備SeLoadDriverPrivilege許可權的使用者才能安裝列印驅動,但為了便於標準使用者安裝驅動,從Windows Vista開始,只要印表機驅動是已經存在的可立即使用的驅動,就不需要任何特權即可安裝。例如,透過一條PowerShell命令即可安裝“Generic / Text-Only”驅動。
在新增一個印表機時,需要設定印表機的埠。Windows支援多種型別的印表機埠: LPT1埠、USB埠、網路埠和檔案等。如果設定埠為檔案,則意味著印表機將資料列印到指定檔案。例如,透過一條PowerShell命令即可新增一個輸出到指定檔案的列印埠。
Add-PrinterPort -Name "C:\windows\Temp\myport.txt"
實際上,該操作是在登錄檔中增加一個REG_SZ型別的值。
準備好驅動和埠後,透過一條PowerShell命令即可建立一個印表機。
印表機建立完畢後,透過一條PowerShell命令即可列印資料到指定埠:
"Print Test!" | Out-Printer -Name "PrintTest"
由於PrintTest印表機的埠是檔案c:\windows\Temp\myport.txt,因此列印命令執行後,資料“Print Test!”將會被寫入(即列印)到該檔案。
針對埠是檔案的列印過程,spooler列印服務程式以impersonating方式來模擬當前使用者的特權進行檔案寫入。因此,如果埠檔案在受保護的系統目錄(例如C:\Windows\system32), 則非管理員下的PowerShell列印作業就會失敗。
離線列印的機制
在Windows系統上,如果系統配置啟用了假離線服務,則所有的列印任務都不是立即執行。相反,系統使用Print Spooler來管理離線列印任務。具體來說,當使用者呼叫列印操作後,系統將列印作業儲存在特定的假離線資料夾中。
預設情況下,Windows生成的離線列印任務檔案為.SPL檔案,此外Windows還會建立字尾名為.SHD的shadow檔案並同SPL檔案做關聯。建立shadow檔案的用途是:在列印程式出現問題或者列印任務被掛起後,Print Spooler依然可以透過SHD檔案恢復列印任務。
在Windows系統重啟或Print Spooler服務重啟之後,.SHD和.SPL檔案會被重新讀取以恢復列印任務。
列印提權的原理
離線印表機制使得Windows系統在重啟後會恢復可能存在的未執行列印任務。但是,重啟後的Printer Spooler服務程式直接使用了System許可權來恢復未執行的列印作業。對於印表機埠為檔案的列印任務,列印檔案的寫入也就在System許可權下被執行。因此,系統重啟使得離線列印任務具備了System許可權的任意檔案寫入能力。
印表機的設定除PowerShell指令碼外,透過系統控制皮膚也能設定。具體來說,透過“裝置和印表機”能新增印表機並設定埠。
但如果設定列印埠名為“C:\Windows\system32\myport.txt”,則會失敗。
為何設定同樣檔名的印表機埠,透過控制皮膚會失敗,而透過PowerShell 命令則可以成功呢?透過分析這兩種方式對spooler程式執行流程的影響,發現spooler程式對透過PowerShell命令列新增印表機埠方式缺乏安全校驗。
具體來講,針對PowerShell命令新增印表機埠,spooler程式直接設定了相應的印表機埠登錄檔項;針對控制皮膚新增印表機埠,spooler程式會首先嚐試建立該埠檔案,建立失敗後就不會再設定相應的登錄檔項。
進一步分析相關API發現,Windows系統提供了兩種新增印表機埠的API,分別是AddPort函式和XcvData函式。其中MSDN對AddPort的描述:
“AddPort函式瀏覽網路以查詢現有埠,並彈出對話方塊供使用者選擇。 AddPort函式應該透過呼叫EnumPorts來驗證使用者輸入的埠名稱,以確保不存在重複的名稱。AddPort函式的呼叫方必須具有訪問埠所連線的伺服器的SERVER_ACCESS_ADMINISTER許可權。要新增埠而不顯示對話方塊,可呼叫XcvData函式而不是AddPort ”。
透過控制皮膚新增印表機在底層是呼叫了AddPort函式,該函式會觸發spooler程式對埠的合法性校驗。透過PowerShell命令新增印表機在底層則是直接呼叫XcvData函式,該函式不會觸發spooler程式對使用者新增的埠進行安全校驗。因此,測試程式AddPort.exe透過該函式在標準使用者許可權下也能設定印表機埠為受保護目錄中的檔案。
漏洞補丁的分析
分析漏洞修復後的版本發現,微軟在關鍵函式LcmCreatePortEntry(最終建立印表機埠的函式)中新增了相應的埠合法性檢查程式碼。下圖是關鍵函式LcmCreatePortEntry在修復前和修復後的Call Graph對比,可以看出:補丁的核心是透過函式PortIsValid對埠進行合法性檢查。
根據上文的分析可知,標準使用者是無法在系統目錄中建立檔案的,把埠設定為系統目錄下的檔案會導致PortIsValid檢測不到目標檔案,從而判定要設定的埠是非法的。因此,在補丁修復後,標準使用者新增列印埠為系統目錄下檔案的印表機就會始終失敗,從而避免了系統重啟時恢復惡意的列印服務。
四、修復建議
由於該漏洞能影響眾多的Windows系統版本,而且可以在標準使用者下發起漏洞攻擊,建議受影響的使用者及時進行系統更新或安裝漏洞補丁。
此外,微軟的安全更新只是對列印埠API進行了更嚴格的校驗。但是,如果惡意檔案埠在漏洞修復前已經建立,則漏洞攻擊實際已經生效,此時進行系統更新仍然是不安全的。建議使用者先使用PowerShell命令Get-PrinterPort來檢查系統中是否存在可疑的印表機埠,在刪除可疑埠後再實施系統更新。
參考資料:
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1048
https://docs.microsoft.com/en-us/windows/win32/printdocs/addport
https://docs.microsoft.com/en-us/previous-versions/ff564255(v%3dvs.85)
https://windows-internals.com/printdemon-cve-2020-1048/