With the release of Windows 10 RS5 the generic UAC bypass I documented in "Reading Your Way Around UAC" (parts 1, 2 and 3) has been fixed. This quick blog post will describe the relatively simple change MS made to the kernel to fix the UAC bypass and some musing on how it still might be possible to bypass.
As a quick recap, the UAC bypass I documented allowed any normal user on the same desktop to open a privileged UAC admin process and get a handle to the process' access token. The only requirement was there was an existing elevated process running on the desktop, but that's a very common behavior. That in itself didn't allow you to do much directly. However by duplicating the token which, made it writable, it was possible to selectively downgrade the token so that it could be impersonated.
Prior to Windows 10 all you needed to do was downgrade the token's integrity level to Medium. This left the token still containing the Administrators group, but it passed the kernel's checks for impersonation. This allows you to directly modify administrator only resources. For Windows 10 an elevation check was introduced which prevented a process in a non-elevated session from impersonating an elevated token. This was indicated by a flag in the limited token's logon session structure. If the flag was set, but you were impersonating an elevated token it'd fail. This didn't stop you from impersonating the token as long as it was considered non-elevated then abusing WMI to spawn a process in that session or the Secondary Logon Service to get back administrator privileges.
Let's look now at how it was fixed. The changed code is in the SeTokenCanImpersonate method which determines whether a token is allowed to impersonated or not.
TOKEN* process_token = ...;
TOKEN* imp_token = ...;
#define LIMITED_LOGON_SESSION 0x4
if (SeTokenIsElevated(imp_token)) {
if (!SeTokenIsElevated(process_token) &&
(process_token->LogonSession->Flags & LIMITED_LOGON_SESSION)) {
return STATUS_PRIVILEGE_NOT_HELD;
}
}
if (process_token->LogonSession->Flags & LIMITED_LOGON_SESSION
&& !(imp_token->LogonSession->Flags & LIMITED_LOGON_SESSION)) {
SepLogUnmatchedSessionFlagImpersonationAttempt();
return STATUS_PRIVILEGE_NOT_HELD;
}
The first part of the code is the same as was introduced in Windows 10. If you try and impersonate an elevated token and your process is running in the limited logon session it'll be rejected. The new check introduced ensures that if you're in the limited logon session you're not trying to impersonate a token in a non-limited logon session. And there goes the UAC bypass, using any variation of the attack you need to impersonate the token to elevate your privileges.
The fix is pretty simple, although I can't help think there must be some edge case which this would trip up. The only case which comes to mind in tokens returned from the LogonUser APIs, however those are special cases earlier in the function so I could imagine this would only be a problem when there might be a more significant security bug.
It's worth bearing in mind that due to the way Microsoft fixes bugs in UAC this will not be ported to versions prior to RS5. So if you're on a Windows Vista through Windows 10 RS4 machine you can still abuse this to bypass UAC, in most cases silently. And there's hardly a lack of other UAC bypasses, you just have to look at UACME. Though I'll admit none of the bypasses are as interesting to me as a fundamental design flaw in the whole technology. The only thing I can say is Microsoft seems committed to fixing these bugs eventually, even if they seem to introduce more UAC bypasses in each release.
Can this fix be bypassed? It's predicated on the user not having control over a process running outside of the limited logon session. A potential counter example would be processes spawned from an elevated process where the token is intentionally restricted, such as in sandboxed applications such as Adobe Reader or Chrome. However in order for that to be exploitable you'd need to convince the user to elevate those applications which doesn't make for a general technique. There's of course potential impersonation bugs, such as my Constrained Impersonation attack which could be used to bypass Over-The-Shoulder elevation but also could be used to impersonate SYSTEM tokens. Bugs like that tend to be something Microsoft want to fix (the Constrained Impersonation one was fixed as CVE-2018-0821) so again not a general technique.
I did have a quick think about other ways of bypassing this, then I realized I don't actually care ;-)