Windows: Token Trust SID Access Check Bypass EOP
Platform: Windows 10 1709 (also tested current build of RS4)
Class: Elevation of Privilege

Summary: A token’s trust SID isn’t reset when setting a token after process creation allowing a user process to bypass access checks for trust labels.

Description:

When a protected process is created it sets the protection inside the EPROCESS structure but also adds a special trust SID to the primary token as part of SeSubProcessToken. Where the process protection is used for things such as what access rights to other processes the trust SID is used for direct access checks where a security descriptor has a process trust label. A good example is the \KnownDlls object directory which is labeled as PPL-WinTcb to prevent tampering from anything not at that protection level.

This trust SID isn’t cleared during duplication so it’s possible for a non-protected process to open the token of a protected process and duplicate it with the trust SID intact. However using that token should clear the SID, or at least cap it to the maximum process protection level. However there’s a missing edge case, when setting a primary token through NtSetInformationProcess (specifically in PspAssignPrimaryToken). Therefore we can exploit this with the following from a normal non-admin process:

1) Create a protected process, werfaultsecure.exe is a good candidate as it’ll run PP-WinTcb. It doesn’t have to do anything special, just be created.
2) Open the process token (we get PROCESS_QUERY_LIMITED_INFORMATION) and duplicate it to a new primary token.
3) Create a new suspended process which will run the exploit code with the original token.
4) Set the protected process token using NtSetInformationProcess
5) Resume exploit process and do something which needs to pass the trust label check.

NOTE: There is also a related issue during impersonation and the call to SeTokenCanImpersonate. Normally the current process trust SID is checked against the impersonation token trust SID and if the process token’s is lower a flag is returned to the caller which resets the new token’s trust SID to the process one. This check occurs before the check for SeImpersonatePrivilege but _after_ the check for an anonymous token authentication ID. Therefore if you’re an admin you could craft a token with the anonymous token authentication ID (but with actual groups) and do a similar trick as with the process token to prevent the reset of the trust SID during impersonation. However I couldn’t find an obvious use for this as the trust label seems to be based on the minimum between the impersonation and process token’s trust SIDs and when impersonating over a boundary such as in RPC it looks like it gets reset to the process’ protection level. But might be worth cleaning this up as well if you’re there.

Proof of Concept:

I’ve provided a PoC as a C# project. It does the previous described trick to run a process which can then set the trust label on a new event object it creates (\BaseNamedObject\PPDEMO). If you run the poc with a command line parameter it will try and do the event creation but should print access denied.

1) Compile the C# project. It will need to grab the NtApiDotNet from NuGet to work.
2) Run the poc with no parameters as a normal user. It will capture the token and respawn itself to create the event.

Expected Result:
Setting the trust label returns access denied.

Observed Result:
The trust label is successfully set.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44630.zip