research.ifcr.dk Open in urlscan Pro
162.159.152.4  Public Scan

Submitted URL: https://t.co/2cfnKmeouu
Effective URL: https://research.ifcr.dk/spoolfool-windows-print-spooler-privilege-escalation-cve-2022-22718-bf7752b68d81?gi=1432dd25f2d1
Submission: On April 25 via api from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

Open in app

Sign In

Get started


Home
Notifications
Lists
Stories

--------------------------------------------------------------------------------

Write




RESPONSES (4)



What are your thoughts?

Cancel
Respond

Also publish to my profile

There are currently no responses for this story.

Be the first to respond.

Published in

IFCR

Oliver Lyak
Follow

Feb 8

·
13 min read
·

Listen



Save







SPOOLFOOL: WINDOWS PRINT SPOOLER PRIVILEGE ESCALATION (CVE-2022-21999)

UPDATE (Feb 9, 2022): Microsoft initially patched this vulnerability without
giving me any information or acknowledgement, and as such, at the time of patch
release, I thought that the vulnerability was identified as CVE-2022–22718,
since it was the only Print Spooler vulnerability in the release without any
acknowledgement. I contacted Microsoft for clarification, and the day after the
patch release, Institut For Cyber Risk and I was acknowledged for
CVE-2022–21999.

In this blog post, we’ll look at a Windows Print Spooler local privilege
escalation vulnerability that I found and reported in November 2021. The
vulnerability got patched as part of Microsoft’s Patch Tuesday in February 2022.
We’ll take a quick tour of the components and inner workings of the Print
Spooler, and then we’ll dive into the vulnerability with root cause, code
snippets, images and much more. At the end of the blog post, you can also find a
link to a functional exploit.




BACKGROUND

Back in May 2020, Microsoft patched a Windows Print Spooler privilege escalation
vulnerability. The vulnerability was assigned CVE-2020–1048, and Microsoft
acknowledged Peleg Hadar and Tomer Bar of SafeBreach Labs for reporting the
security issue. On the same day of the patch release, Yarden Shafir and Alex
Ionescu published a technical write-up of the vulnerability. In essence, a user
could write to an arbitrary file by creating a printer port that pointed to a
file on disk. After the vulnerability (CVE-2020–1048) had been patched, the
Print Spooler would now check if the user had permissions to create or write to
the file before adding the port. A week after the release of the patch and blog
post, Paolo Stagno (aka VoidSec) privately disclosed a bypass for CVE-2020–1048
to Microsoft. The bypass was patched three months later in August 2020, and
Microsoft acknowledged eight independent entities for reporting the
vulnerability, which was identified as CVE-2020–1337. The bypass for the
vulnerability used a directory junction (symbolic link) to circumvent the
security check. Suppose a user created the directory C:\MyFolder\ and configured
a printer port to point to the file C:\MyFolder\Port. This operation would be
granted, since the user is indeed allowed to create C:\MyFolder\Port. Now, what
would happen if the user then turned C:\MyFolder\ into a directory junction that
pointed to C:\Windows\System32\ after the port had been created? Well, the
Spooler would simply write to the file C:\Windows\System32\Port.

These two vulnerabilities, CVE-2020–1048 and CVE-2020–1337, were patched in May
and August 2020, respectively. In September 2020, Microsoft patched a different
vulnerability in the Print Spooler. In short, this vulnerability allowed users
to create arbitrary and writable directories by configuring the SpoolDirectory
attribute on a printer. What was the patch? Almost the same story: After the
patch, the Print Spooler would now check if the user had permissions to create
the directory before setting the SpoolDirectory property on a printer. Perhaps
you can already see where this post is going. Let’s start at the beginning.




INTRODUCTION TO SPOOLER COMPONENTS

The Windows Print Spooler is a built-in component on all Windows workstations
and servers, and it is the primary component of the printing interface. The
Print Spooler is an executable file that manages the printing process.
Management of printing involves retrieving the location of the correct printer
driver, loading that driver, spooling high-level function calls into a print
job, scheduling the print job for printing, and so on. The Spooler is loaded at
system startup and continues to run until the operating system is shut down. The
primary components of the Print Spooler are illustrated in the following
diagram.


https://docs.microsoft.com/en-us/windows-hardware/drivers/print/introduction-to-spooler-components

Application

The print application creates a print job by calling Graphics Device Interface
(GDI) functions or directly into winspool.drv.

GDI

The Graphics Device Interface (GDI) includes both user-mode and kernel-mode
components for graphics support.

winspool.drv

winspool.drv is the client interface into the Spooler. It exports the functions
that make up the Spooler’s Win32 API, and provides RPC stubs for accessing the
server.

spoolsv.exe

spoolsv.exe is the Spooler’s API server. It is implemented as a service that is
started when the operating system is started. This module exports an RPC
interface to the server side of the Spooler’s Win32 API. Clients of spoolsv.exe
include winspool.drv (locally) and win32spl.dll (remotely). The module
implements some API functions, but most function calls are passed to a print
provider by means of the router (spoolss.dll).

Router

The router, spoolss.dll, determines which print provider to call, based on a
printer name or handle supplied with each function call, and passes the function
call to the correct provider.

Print Provider

The print provider that supports the specified print device.


LOCAL PRINT PROVIDER

The local print provider provides job control and printer management
capabilities for all printers that are accessed through the local print
provider’s port monitors.

The following diagram provides a view of control flow among the local printer
provider’s components, when an application creates a print job.


https://docs.microsoft.com/en-us/windows-hardware/drivers/print/introduction-to-print-providers

While this control flow is rather large, we will mostly focus on the local print
provider localspl.dll.




VULNERABILITY

The vulnerability consists of two bypasses for CVE-2020–1030. I highly recommend
reading Victor Mata’s blog post on CVE-2020–1030, but I’ll try to cover the
important parts as well.

When a user prints a document, a print job is spooled to a predefined location
referred to as the “spool directory”. The spool directory is configurable on
each printer and it must allow the FILE_ADD_FILE permission to all users.


Permissions of the default spool directory

Individual spool directories are supported by defining the SpoolDirectory value
in a printer’s registry key HKLM\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Print\Printers\<printer>.

The Print 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 SetPrinterDataEx. This function
requires a printer to be opened with the PRINTER_ACCESS_ADMINISTER access right.
If the current user doesn’t have permission to open an existing printer with the
PRINTER_ACCESS_ADMINISTER access right, there are two options:

 * The user can create a new local printer
 * The user can add a remote printer

By default, users in the INTERACTIVE group have the “Manage Server” permission
and can therefore create new local printers, as shown below.


Security of the local print server on Windows desktops

However, it seems that this permission is only granted on Windows desktops, such
as Windows 10 and Windows 11. During my testing on Windows servers, this
permission was not present. Nonetheless, it was still possible for users without
the “Manage Server” permission to add remote printers.



If a user adds a remote printer, the printer will inherit the security
properties of the shared printer from the printer server. As such, if the remote
printer server allows Everyone to manage the printer, then it’s possible to
obtain a handle to the printer with the PRINTER_ACCESS_ADMINISTER access right,
and SetPrinterDataEx would update the local registry as usual. A user could
therefore create a shared printer on a different server or workstation, and
grant Everyone the right to manage the printer. On the victim server, the user
could then add the remote printer, which would now be manageable by Everyone.
While I haven’t fully tested how this vulnerability behaves on remote printers,
it could be a viable option for situations, where the user cannot create or
mange a local printer. But please note that while some operations are handled by
the local print provider, others are handled by the remote print provider.



When we have opened or created a printer with the PRINTER_ACCESS_ADMINISTER
access right, we can configure the SpoolDirectory on it.



When calling SetPrinterDataEx, an RPC request will be sent to the local Print
Spooler spoolsv.exe, which will route the request to the local print provider’s
implementation in localspl.dll!SplSetPrinterDataEx. The control flow consists of
the following events:

 * 1. spoolsv.exe!SetPrinterDataEx routes to SplSetPrinterDataEx in the local
   print provider localspl.dll
 * 2. localspl.dll!SplSetPrinterDataEx validates permissions before restoring
   SYSTEM context and modifying the registry via localspl.dll!SplRegSetValue

In the case of setting the SpoolDirectory value,
localspl.dll!SplSetPrinterDataEx will verify that the provided directory is
valid before updating the registry key. This check was not present before
CVE-2020–1030.


localspl.dll!SplSetPrinterDataEx

Given a path to a directory, localspl.dll!IsValidSpoolDirectory will call
localspl.dll!AdjustFileName to convert the path into a canonical path. For
instance, the canonical path for C:\spooldir\ would be \\?\C:\spooldir\, and if
C:\spooldir\ is a symbolic link to C:\Windows\System32\, the canonical path
would be \\?\C:\Windows\System32\. Then, localspl.dll!IsValidSpoolDirectory will
check if the current user is allowed to open or create the directory with
GENERIC_WRITE access right. If the directory was successfully created or opened,
the function will make a final check that the number of links to the directory
is not greater than 1, as returned by GetFileInformationByHandle.


localspl.dll!IsValidSpoolDirectory

So in order to set the SpoolDirectory, the user must be able to create or open
the directory with writable permissions. If the validation succeeds, the print
provider will update the printer’s SpoolDirectory registry key. However, the
spool directory will not be created by the Print Spooler until it has been
reinitialized. This means that we will need to figure out how to restart the
Spooler service (we will come back to this part), but it also means that the
user only needs to be able to create the directory during the validation when
setting the SpoolDirectory registry key — and not when the directory is actually
created.

In order to bypass the validation, we can use reparse points (directory
junctions in this case). Suppose we create a directory named C:\spooldir\, and
we set the SpoolDirectory to C:\spooldir\printers\. The Spooler will check that
the user can create the directory printers inside of C:\spooldir\. The
validation passes, and the SpoolDirectory gets set to C:\spooldir\printers\.
After we have configured the SpoolDirectory, we can convert C:\spooldir\ into a
reparse point that points to C:\Windows\System32\. When the Spooler initializes,
the directory C:\Windows\System32\printers\ will be created with writable
permissions for everyone. If the directory already exists, the Spooler will not
set writable permissions on the folder.

As such, we need to find an interesting place to create a directory. One such
place is C:\Windows\System32\spool\drivers\x64\, also known as the printer
driver directory (on other architectures, it’s not x64). The printer driver
directory is particularly interesting, because if we call SetPrinterDataEx with
the CopyFiles registry key, the Spooler will automatically load the DLL assigned
in the Module value — if the Module file path is allowed.



This event is triggered when pszKeyName begins with the CopyFiles\ string. It
initiates a sequence of functions leading to LoadLibrary.


localspl.dll!SplSetPrinterDataEx

The control flow consists of the following events:

 * 1. spoolsv.exe!SetPrinterDataEx routes to SplSetPrinterDataEx in the local
   print provider localspl.dll
 * 2. localspl.dll!SplSetPrinterDataEx validates permissions before restoring
   SYSTEM context and modifying the registry via localspl.dll!SplRegSetValue
 * 3. localspl.dll!SplCopyFileEvent is called if pszKeyName argument begins with
   CopyFiles\ string
 * 4. localspl.dll!SplCopyFileEvent reads the Module value from printer’s
   CopyFiles registry key and passes the string to
   localspl.dll!SplLoadLibraryTheCopyFileModule
 * 5. localspl.dll!SplLoadLibraryTheCopyFileModule performs validation on the
   Module file path
 * 6. If validation passes, localspl.dll!SplLoadLibraryTheCopyFileModule
   attempts to load the module with LoadLibrary

The validation steps consist of localspl.dll!MakeCanonicalPath and
localspl.dll!IsModuleFilePathAllowed. The function
localspl.dll!MakeCanonicalPath will take a path and convert it into a canonical
path, as described earlier.


localspl.dll!MakeCanonicalPath

localspl.dll!IsModuleFilePathAllowed will validate that the canonical path
either resides directly inside of C:\Windows\System32\ or within the printer
driver directory. For instance, C:\Windows\System32\payload.dll would be
allowed, whereas C:\Windows\System32\Tasks\payload.dll would not. Any path
inside of the printer driver directory is allowed, e.g.
C:\Windows\System32\spool\drivers\x64\my\path\to\payload.dll is allowed. If we
are able to create a DLL in C:\Windows\System32\ or anywhere in the printer
driver directory, we can load the DLL into the Spooler service.


localspl.dll!SplLoadLibraryTheCopyFileModule

Now, we know that we can use the SpoolDirectory to create an arbitrary directory
with writable permissions for all users, and that we can load any DLL into the
Spooler service that resides in either C:\Windows\System32\ or the printer
driver directory. There is only one issue though. As mentioned earlier, the
spool directory is created during the Spooler initialization. The spool
directory is created when localspl.dll!SplCreateSpooler calls
localspl.dll!BuildPrinterInfo. Before localspl.dll!BuildPrinterInfo allows
Everybody the FILE_ADD_FILE permission, a final check is made to make sure that
the directory path does not reside within the printer driver directory.


localspl.dll!BuildPrinterInfo

This means that a security check during the Spooler initialization verifies that
the SpoolDirectory value does not point inside of the printer driver directory.
If it does, the Spooler will not create the spool directory and simply fallback
to the default spool directory. This security check was also implemented in the
patch for CVE-2020-1030.

To summarize, in order to load the DLL with
localspl.dll!SplLoadLibraryTheCopyFileModule, the DLL must reside inside of the
printer driver directory or directly inside of C:\Windows\System32\. To create
the writable directory during Spooler initialization, the directory must not
reside inside of the printer driver directory. Both
localspl.dll!SplLoadLibraryTheCopyFileModule and localspl.dll!BuildPrinterInfo
check if the path points inside the printer driver directory. In the first case,
we must make sure that the DLL path begins with
C:\Windows\System32\spool\drivers\x64\, and in the second case, we must make
sure that the directory path does not begin with
C:\Windows\System32\spool\drivers\x64\.

During the both checks, the SpoolDirectory is converted to a canonical path, so
even if we set the SpoolDirectory to C:\spooldir\printers\ and then convert
C:\spooldir\ into a reparse point that points to
C:\Windows\System32\spool\drivers\x64\, the canonical path will still become
\\?\C:\Windows\System32\spool\drivers\x64\printers\. The check is done by
stripping of the first four bytes of the canonical path, i.e.
\\?\C:\Windows\System32\spool\drivers\x64\ printers\ becomes
C:\Windows\System32\spool\drivers\x64\ printers\, and then checking if the path
begins with the printer driver directory C:\Windows\System32\spool\drivers\x64\.
And so, here comes the second bug to pass both checks.

If we set the spooler directory to a UNC path, such as
\\localhost\C$\spooldir\printers\ (and C:\spooldir\ is a reparse point to
C:\Windows\System32\spool\drivers\x64\), the canonical path will become
\\?\UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\, and during
comparison, the first four bytes are stripped, so
UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\ is compared to
C:\Windows\System32\spool\drivers\x64\ and will no longer match. When the
Spooler initializes, the directory
\\?\UNC\localhost\C$\Windows\System32\spool\drivers\x64\printers\ will be
created with writable permissions. We can now write our DLL into
C:\Windows\System32\spool\drivers\x64\printers\payload.dll. We can then trigger
the localspl.dll!SplLoadLibraryTheCopyFileModule, but this time, we can just
specify the path normally as
C:\Windows\System32\spool\drivers\x64\printers\payload.dll.

We now have the primitives to create a writable directory inside of the printer
driver directory and to load a DLL within the driver directory into the Spooler
service. The only thing left is to restart the Spooler service such that the
directory will be created. We could wait for the server to be restarted, but
there is a technique to terminate the service and rely on the recovery to
restart it. By default, the Spooler service will restart on the first two
“crashes”, but not on subsequent failures.

To terminate the service, we can use
localspl.dll!SplLoadLibraryTheCopyFileModule to load
C:\Windows\System32\AppVTerminator.dll. 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. This technique was explained for
CVE-2020-1030 by Victor Mata from Accenture.

Here’s a full exploit in action. The DLL used in this example will create a new
local administrator named “admin”. The DLL can also be found in the exploit
repository.


SpoolFool in action

The steps for the exploit are the following:

 * Create a temporary base directory that will be used for our spool directory,
   which we’ll later turn into a reparse point
 * Create a new local printer named “Microsoft XPS Document Writer v4”
 * Set the spool directory of our new printer to be our temporary base directory
 * Create a reparse point on our temporary base directory to point to the
   printer driver directory
 * Force the Spooler to restart to create the directory by loading
   AppVTerminator.dll into the Spooler
 * Write DLL into the new directory inside of the printer driver directory
 * Load the DLL into the Spooler

Remember that it is sufficient to create the driver directory only once in order
to load as many DLLs as desired. There’s no need to trigger the exploit multiple
times, doing so will most likely end up killing the Spooler service indefinitely
until a reboot brings it back up. When the driver directory has been created, it
is possible to keep writing and loading DLLs from the directory without
restarting the Spooler service. The exploit that can be found at the end of this
post will check if the driver directory already exists, and if so, the exploit
will skip the creation of the directory and jump straight to writing and loading
the DLL. The second run of the exploit can be seen below.


Second run of SpoolFool

The functional exploit and DLL can be found here:
https://github.com/ly4k/SpoolFool.


CONCLUSION

That’s it. Microsoft has officially released a patch. When I initially found out
that there was a check during the actual creation of the directory as well, I
started looking into other interesting places to create a directory. I found
this post by Jonas L from Secret Club, where the Windows Error Reporting Service
(WER) is abused to exploit an arbitrary directory creation primitive. However,
the technique didn’t seem to work reliably on my Windows 10 machine. The
SplLoadLibraryTheCopyFileModule is very reliable however, but assumes that the
user can manage a printer, which is already the case for this vulnerability.


PATCH ANALYSIS

[Feb 08, 2022]

A quick check with Process Monitor reveals that the SpoolDirectory is no longer
created when the Spooler initializes. If the directory does not exist, the Print
Spooler falls back to the default spool directory.

To be continued…


DISCLOSURE TIMELINE

 * Nov 12, 2021: Reported to Microsoft Security Response Center (MSRC)
 * Nov 15, 2021: Case opened and assigned
 * Nov 19, 2021: Review and reproduction
 * Nov 22, 2021: Microsoft starts developing a patch for the vulnerability
 * Jan 21, 2022: The patch is ready for release
 * Feb 08, 2022: Patch is silently released. No acknowledgement or information
   received from Microsoft
 * Feb 09, 2022: Microsoft gives acknowledgement to me and Institut For Cyber
   Risk for CVE-2022-21999




10



4



10

10

4





MORE FROM IFCR

Follow

Institut For Cyber Risk

Michael Dyrmose

·Feb 8


FIRST!

Today we are launching the IFCR research blog. The ambition is to provide
original research and new technical ideas and approaches to the offensive
security community. New material will be added occasionally and not by a defined
schedule. That being said, we’ve already got quite a few interesting posts
lined-up. …

1 min read




--------------------------------------------------------------------------------

Read more from IFCR


RECOMMENDED FROM MEDIUM

Daniella Kerekes

{UPDATE} SOLITAIRE: FARM AND FAMILY HACK FREE RESOURCES GENERATOR



Chintan Patel

END USER LICENSE AGREEMENT



Treasure Hunt Game

HOW TO PLAY ON THE TESTNET



METACOIN

in

Metacoin

INBLOCK RUNS ON IBM’S LINUXONE INTO DIGITAL ASSET STORAGE



Numair Faraz

THE INCOMPETENCE THAT WILL KILL APPLE



Andriy Kusyy

TIMELINE OF MASSIVE CYBERATTACKS AND MISTAKES BEHIND THEM



Ankita Sinha

WHAT ARE ZERO-KNOWLEDGE TECHNIQUES AND ZERO-KNOWLEDGE PROOFS (ZKPS)



ECS Corporation

MAJOR CYBER THREATS IN BANKING AND FINANCE SECTOR (BFSI) ORGANIZATION IN 2020


Get started

Sign In


OLIVER LYAK



36 Followers



Twitter: https://twitter.com/ly4k_ Github: https://github.com/ly4k/


Follow



MORE FROM MEDIUM

jeremyah joel

RUNNER UP AT BPJS KESEHATAN SECURITY HACKATHON



ZeusCybersec

HACK THE BOX — NIBBLES



A. Boukar

in

InfoSec Write-ups

HOST HEADER INJECTION ATTACKS



BaudSkidNinja

UART- SHELL ACCESS TO ROUTER



Help

Status

Writers

Blog

Careers

Privacy

Terms

About

Knowable

To make Medium work, we log user data. By using Medium, you agree to our Privacy
Policy, including cookie policy.