Windows: NtCreateLowBoxToken Handle Capture Local DoS/Elevation of Privilege
Platform: Windows 8.1 Update, Windows 10, Windows Server 2012
Class: Local Dos/Elevation of Privilege
The NtCreateLowBoxToken API allows the capture of arbitrary handles which can lead to to local DoS or elevation of privilege.
The NtCreateLowBoxToken system call accepts an array of handles which are stored with the new token. This is presumably for maintaining references to the appcontainer specific object directories and symbolic links so that they do not need to be maintained anywhere else. The function, SepReferenceLowBoxObjects which captures the handles has a couple of issues which can lead to abuse:
1) It calls ZwDuplicateObject which means the API can capture kernel handles as well as user handles.
2) No checks are made on what object types the handles represent.
The fact that kernel handles can be captured isn’t as bad as it could be. As far as I can tell there’s no way of getting the handles back. The second issue though is slightly more serious as it allows a user to create a reference cycle to kernel objects and potentially maintain them indefinitely, at least until a reboot.
One way of doing this is to exploit the fact that threads can be assigned impersonation tokens. For example a new thread can be created and the handle to that thread captured inside the lowbox handle table. The resulting lowbox token can then be assigned as an impersonation token, the thread and token now maintain their references and the kernel objects survive the user logging out. As the thread references the process this also maintains the process object.
Now at the point of logging out the process will be terminated but because the token maintains the reference cycle the process object itself will not go away. This can lead to a few results:
1) A user could open handles to important resources and files and prevent the handles getting released. This could ultimately result in a local DoS (although only something like a terminal server would be affected) and the administrator wouldn’t easily be able to fix it without rebooting as the process becomes hidden from typical task managers and trying to terminate it won’t help.
2) If a user logs out then back in again they can reopen the process (by PID or using NtGetNextProcess) and get access to the original process token which is still marked as having the original session ID (something which would normally require TCB privilege to change). This might be exploitable to elevate privileges in some scenarios.
While the session object still exists in the kernel due to the reference cycle, it is dead so trying to create a process within that session will not work, however the user could release the reference cycle by clearing the thread’s impersonation token which will let session object be cleaned up and allow another user (again think terminal server) to login with that session ID. The user could then create a process in that session indirectly by impersonating the token and using something like the task scheduler.
It isn’t immediately clear if the user would be able to access the session’s desktop/window station due to its DACL, but at the least references to the sessions object directory could be maintained (such as DosDevices) which might allow the user to redirect named resources for the user to themselves and get the privileges of the other user. This would be particularly serious if the other user was an administrator.
Proof of Concept:
I’ve provided a PoC which will cause the reference cycle and display the process if it can open one. The archive password is ‘password’. Follow these steps:
1) Extract the PoC to a location on a local hard disk which is writable by a normal user
2) Execute the poc executable file
3) The user should be automatically logged out
4) Log back in as the user
5) Execute poc again, it should now print out information about the stuck process and the extracted process token.
It shouldn’t be possible to generate a kernel object reference cycle
The reference cycle is created and the user can reopen the process.
This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public.