Exploit-DB #43465
Description: Microsoft Windows - Local XPS Print Spooler Sandbox Escape
Type: local
Platform: windows
Date: 2018-01-08
Author: Google Security Research

Exploit:
Windows: Local XPS Print Spooler Sandbox Escape
Platform: Windows 10 1703 and 1709 (not tested Windows 7 or 8.x)
Class: Elevation of Privilege

Summary:

The local print spooler can be abused to create an arbitrary file from a low privilege application including one in an AC as well as a typical Edge LPAC CP leading to EoP.

Description:

When creating an XPS print job it's possible to specify the destination file in the DOC_INFO_1 structure passed to StartDocPrinter. When you call WritePrinter to write to the new printer job the privileged printer spooler service impersonates the caller and ensures that they can write to the target. This should ensure that a sandboxed user can't write to a location they don't have access to normally. Unfortunately the spooler then deletes this file it's created under impersonation and then calls NSecurityLibrary::ElevateIntegrityLevelIfLow to increase the IL of caller's token to Medium level if the token is current Low IL. In a naive sandbox such as IE PM this results in the actual file being written as at Medium IL which would be sufficient for writing to any user controlled location such as the Startup folder. However in an AC sandbox you'd assume this wouldn't help as the AC would still be enforced even if the IL of the token was raised. It seems not, if code raises the IL of the AC token to medium (which requires SeTcbPrivilege) then the kernel also removes all traces of the AC, leaving the final token a normal medium IL user token again. Therefore in both the naive and AC cases there exists a TOCTOU attack where you can get the sandboxed token to write to a folder you control then redirect the write to another location once the token IL is raised.

The simplest way of doing this would be your standard symbolic link attacks, fortunately Windows has mitigated all the easy ways of doing such an attack. Unfortunately there's a bug in the handling of NtImpersonateAnonymousToken when running in AC which allows a symlink attack in this specific case. I've submitted the bug in NtImpersonateAnonymousToken as a separate issue. Of course there's no reason to believe that there's no other way of exploiting this issue given enough effort without the bug in NtImpersonateAnonymousToken.

To exploit do the following:

1) Create a fake destination directory in a AC writable directory such as Temp. e.g. if you want to write to c:\users\user\desktop\hello.txt create %TEMP%\users\user\desktop.
2) Use bug in NtImpersonateAnonymousToken to impersonate the non-AC token and create a new C: drive symlink in the anonymous user's drive map pointing at the temp directory. Note that as this is created inside a sandbox a non-sandboxed caller will NOT follow the symlink.
3) Build a native NT path in Win32 form to the target path via the anonymous token's device map directory and pass to StartDocPrinter in DOC_INFO_1. e.g. \\?\GLOBALROOT\Sessions\0\DosDevices\00[PII_REDACTED]03E6\C:\Users\user\desktop\hello.txt
4) Create the "fake" target file in the temp directory and put an exclusive oplock on it.
5) Call WritePrinter in another thread, in original thread wait for the oplock to complete. The open in the print spooler will follow the symlink in this case as it's impersonating the sandboxed token.
6) Delete the symlink and break the oplock, this allows the spooler to continue.
7) The spooler now impersonates the medium user token and tried to open the path. The C: symlink created in 2 now no longer exists, however as we're using a device map directory then the global devicemap fallback will kick in so that the spooler sees the global C: drive.
8) The spooler writes arbitrary data to the new target file outside of the sandboxed area.

I really don't get why the token is elevated before writing the file. There is a mode where if you don't specify a path then the spooler will write the file to the local documents directory. As the sandboxed application has no control of the path it at least makes some sense to elevate to allow the file to be written but when writing an explicit path it seems unnecessary. Note that this also works from LPAC, at least as implemented for Edge CP's. This is because the ALPC port of the spooler has an ACE with the “lpacPrinting” capability which is in the list of capabilities in most (all?) CP's for Edge. I also note that WDAG supports writing XPS files, but I don’t have the time to work out the details of how WDAG works right now to see if it would also be vulnerable.

Proof of Concept:

I’ve provided a PoC as a C# project. The PoC will drop the file hello.txt to the current user’s desktop with arbitrary contents. The PoC will respawn itself as the Microsoft Edge AC and then execute the exploit. You must run this as a UAC split token admin. Note that this ISN’T a UAC bypass, just that a split-token admin has a trivial way of getting a non-AC token by requesting the linked token. The PoC will execute just using a normal AC, to test with LPAC pass the executable any argument you like, the LPAC capabilities are copied from an Edge CP so should be representative of what’s available in real life. It seems on some systems the .NET framework directory has an incorrect DACL which results in the LPAC mode failing. A fresh install of 1709 should work though.

1) Compile the C# project. It will need to grab the NtApiDotNet from NuGet to work. Ensure the main executable and DLLs are in a user writable location (this is needed to tweak the file permissions for AC).
2) Execute the PoC as normal user level split-token admin.
3) Once complete a dialog should appear indicating the operation is Done.

Expected Result:
Writing to a file outside of a sandbox accessible directory should fail.

Observed Result:
The file hello.txt is created in the current user’s desktop directory with arbitrary contents.

Microsoft have made the decision that as the issue with NtImpersonateAnonymousToken (https://bugs.chromium.org/p/project-zero/issues/d