Discovering, exploiting and shutting down a dangerous Windows print spooler vulnerability
November 23, 2020
November 23, 2020
In May 2020, FusionX reported an elevation of privilege vulnerability to the Microsoft Security Response Center (MSRC). The vulnerability affected the application logic implemented in the Windows Print Spooler service. It can be exploited by unprivileged users to attain arbitrary code execution as SYSTEM. Exploitation involves chaining several primitives to load an arbitrary DLL into the print spooler process. Microsoft addressed the issue in CVE-2020-1030, released in September. This post walks through the technical details and illustrates the exploit development process.
The vulnerability emerges when a user adds a printer to a Windows system. By default, users can add printers without administrator privileges. This is only applicable if the printer uses a preinstalled or inbox driver. We use the following code to add a printer with the Microsoft Print To PDF driver.
<<< Start >>>
<<< End >>>
Calling AddPrinter returns a printer handle with the PRINTER_ALL_ACCESS permission, granting access rights to standard and administrative print operations. This behavior is further outlined in Microsoft’s documentation:
The caller of the AddPrinter function must have SERVER_ACCESS_ADMINISTER access to the server on which the printer is to be created. The handle returned by the function will have PRINTER_ALL_ACCESS permission, and can be used to perform administrative operations on the printer. |
While it seems illogical to have SERVER_ACCESS_ADMINISTER permission as an unprivileged user, that is the intended configuration according to the Print Server security properties (Figure 2). The INTERACTIVE security identifier has the Manage Server permission enabled, which corresponds to SERVER_ACCESS_ADMINISTER. Possessing a subset of these permissions is referred to as a partial delegated print administrator.
<<< Start >>>
<<< End >>>
The printer handle, as a result of AddPrinter, introduces spooler APIs, which are inaccessible from an unprivileged context. However, administrative access is only scoped to the printer object, limiting the functions at our disposal. The subsequent sections will demonstrate how the handle is leveraged to undermine the application’s logic.
Point and Print is one of several printer sharing technologies designed for driver distribution. In Point and Print, driver (prior to v4 model) and configuration files are automatically downloaded from the print server. Installation is extendable with a custom Point and Print DLL. The library is implemented by defining the CopyFiles registry key within the printer’s configuration.
Printer configurations are stored as individual subkeys under HKLM\Software\Microsoft\Windows NT\CurrentVersion\Print\Printers. The spooler provides APIs for managing configuration data such as EnumPrinterData, GetPrinterData, SetPrinterData, and DeletePrinterData. Underneath, these functions perform registry operations relative to the printer’s key.
We can modify a printer’s configuration with SetPrinterData and its extended version, SetPrinterDataEx. These functions require a printer handle with PRINTER_ACCESS_ADMINISTER. Users can retrieve a handle with functions such as OpenPrinter or, in our case, AddPrinter as previously illustrated.
<<< Start >>>
<<< End >>>
SetPrinterDataEx with the CopyFiles registry key causes the spooler to automatically load the Point and Print DLL assigned in the Module value (Figure 3). This event is triggered when pszKeyName begins with the CopyFiles\ string (Figure 4). It initiates a sequence of functions leading to LoadLibrary and LoadLibraryEx – Windows API for mapping a DLL into the current process.
<<< Start >>>
<<< End >>>
The control flow consists of the following events:
<<< Start >>>
<<< End >>>
<<< Start >>>
<<< End >>>
The print spooler initially attempts loading the Point and Print DLL from the system directory. If this fails, it performs an additional attempt using a new path sourced from the spooler, driver, environment, and driver version directories. We can observe this behavior by intentionally calling SetPrinterDataEx with an invalid module.
<<< Start >>>
<<< End >>>
The spooler searches the following paths when loading a Point and Print DLL.
Notice the PATH NOT FOUND result in Figure 6. This path refers to the version 4 driver directory. Based on our testing, the version 4 driver directory is absent on Windows systems. Its absence may correspond with the introduction of the v4 driver model in Windows 8 and Windows Server 2012.
The missing path indicated a potential code execution opportunity if we can create the directory with read and write permissions. An arbitrary DLL can then be placed into the file path and invoked via SetPrinterDataEx. Unfortunately, the environment directory (x64) inherits its DACL from its parent directory, preventing unprivileged users from simply creating the missing path.
<<< Start >>>
<<< End >>>
<<< Start >>>
<<< End >>>
When a user prints a document, a print job is spooled to a predefined location referred to as the spool directory. The default location is C:\Windows\System32\spool\PRINTERS. To maintain relevance, there are two important aspects to note:
<<< Start >>>
<<< End >>>
Individual spool directories are supported by defining the SpoolDirectory value in a printer’s registry key. If unspecified, the printer is mapped to the DefaultSpoolDirectory instead. The spool directory is created (or mapped if it exists) when localspl!SplCreateSpooler calls localspl!BuildPrinterInfo. This has only been observed when the spooler service initializes; therefore, changes to a printer’s spool directory aren’t reflected until service has restarted. We will revisit spooler initialization momentarily.
<<< Start >>>
<<< End >>>
Once the printer object is created (Figure 1), we leverage the returned handle to call SetPrinterDataEx and configure the printer’s spool directory. Keep in mind, SetPrinterDataEx requires administrative permission, which is afforded with the handle’s PRINTER_ALL_ACCESS access rights.
<<< Start >>>
<<< End >>>
*Note: The pszKeyName argument is relative to the printer's registry key. Pass a backslash to pszKeyName to set a value in the root and not a subkey. This will allow access to the printer's main configuration values. |
The print spooler service must reinitialize for the spool directory to take effect. Standard users are restricted from restarting system services, and thereby limited to two options:
Our efforts thus far have focused on loading arbitrary Point and Print libraries. However, this is only necessary if we want to load arbitrary DLLs. Our code execution primitive is perfectly applicable (and free from additional stipulations) to existing files in the System32 directory.
Enter AppVTerminator.dll. This library is a signed Microsoft binary included in Windows (confirmed on Windows 10). When loaded into spooler, the library calls TerminateProcess which subsequently kills the spoolsv.exe process. This event triggers the recovery mechanism in the Service Control Manager which in turn starts a new spooler process.
<<< Start >>>
<<< End >>>
<<< Start >>>
<<< End >>>
We can leverage SetPrinterDataEx to set AppVTerminator.dll as a Point and Print DLL. Specifying the Module value name will invoke the Point and Print behavior. The spoolsv.exe service will immediately restart as a result of loading the library.
Spooler initialization can be delayed from a few seconds up to several minutes once the service has restarted. This can pose a minor inconvenience in a time-sensitive operation. After evaluating several APIs, EnumPrinters proved most reliable for invoking localspl!BuildPrinterInfo. Executing EnumPrinters sets off the following chain of calls:
Our proof of concept implements some logic to monitor the service and spool directory. Once those threads have started, we issue multiple calls to EnumPrinters to expedite initialization.
Getting a handle on code execution
When the service initializes, the spool directory (spool\drivers\x64\4) is created with a SECURITY_DESCRIPTOR granting all users with WriteData permission. This allows us to move our payload into the directory.
Our payload, imitating as a Point and Print DLL, must export the SpoolerCopyFileEvent function. This function is called once the module is loaded into the process.
<<< Start >>>
<<< End >>>
To obtain code execution, we use OpenPrinter to request a handle to our existing printer object (Figure 1). SetPrinterDataEx is called once more to trigger Point and Print with our payload. Figure 16 shows the file path argument sent to LoadLibraryEx, which is responsible for loading the module into the spoolsv.exe process.
<<< Start >>>
<<< End >>>
<<< Start >>>
<<< End >>>
The Windows Print Spooler is a complex ecosystem with a code base dating as far back as NT4. And, in combination with new capabilities added in later Windows versions, the print spooler is prime for logic-based vulnerabilities. This is evident in the uptick of spooler-related findings reported in 2020.[i]
Our proof of concept is available on our Github. We would like to thank Microsoft’s Security Response Center and Bryan Alexander for his guidance and contribution.
Date | Event |
2020-05-06 | Report submitted to Microsoft Security Response Center |
2020-05-07 | Case 58551 opened |
2020-05-14 | Microsoft requested additional information |
2020-05-15 | Provided additional files for reproduction |
2020-06-08 | Requested status update |
2020-06-10 | Received update, vulnerability successfully reproduced |
2020-06-18 | Received update, fix scheduled for September release |
2020-09-08 | Vulnerability addressed in CVE-2020-1030 |
Accenture Security is a leading provider of end-to-end cybersecurity services, including advanced cyber defense, applied cybersecurity solutions and managed security operations. We bring security innovation, coupled with global scale and a worldwide delivery capability through our network of Advanced Technology and Intelligent Operations centers. Helped by our team of highly skilled professionals, we enable clients to innovate safely, build cyber resilience and grow with confidence. Follow us @AccentureSecure on Twitter or visit us at www.accenture.com/security.
Copyright © 2020 Accenture. All rights reserved. Accenture, and its logo are trademarks of Accenture.
This document is produced by consultants at Accenture as general guidance. It is not intended to provide specific advice on your circumstances. If you require advice or further details on any matters referred to, please contact your Accenture representative. Given the inherent nature of this document, the content contained in this article is based on information gathered and understood at the time of its creation. It is subject to change. Accenture provides the information on an “as-is” basis without representation or warranty and accepts no liability for any action or failure to act taken in response to the information contained or referenced in this article.
___
[i] List of Print Spooler CVEs:
https://nvd.nist.gov/vuln/detail/CVE-2019-0759
https://www.cyberscoop.com/windows-print-spooler-safebreach-black-hat/