A customer had a service running as Local System and wanted to change some token information. The information that they wanted to change required SeTcbPrivilege
, so they adjusted their token privileges to enable that privilege, but the call still failed with ERROR_
PRIVILEGE_
NOT_
HELD
: “A required privilege is not held by the client.”
Here’s sketch of their code. All function calls succeed except the last one.
HANDLE processToken; OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &processToken); HANDLE newToken; DuplicateTokenEx(processToken, TOKEN_ALL_ACCESS, nullptr, SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &newToken); TOKEN_PRIVILEGES privileges; privileges.PrivilegeCount = 1; privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; LookupPrivilegeValue(nullptr, SE_TCB_NAME, &privileges.Privileges[0].Luid); AdjustTokenPrivileges(newToken, FALSE, &privileges, 0, nullptr, nullptr); DWORD sessionId = ...; SetTokenInformation(newToken, TokenSessionId, &sesionId, sizeof(sessionId)); // FAILS!
This fails because we adjusted the privileges of the wrong token!
The TCB privilege needs to be enabled on the token that is performing the operation, not the token that is the target of the operation. Because you need privileges to do things, not to have things done to you.
The security folks explained that the correct order of operations is
ImpersonateSelf()
.OpenThreadToken()
.AdjustTokenPrivileges(threadToken)
.- Do the thing you wanna do. (In this case, duplicate the token and change the session ID.)
- Close the thread token.
RevertToSelf()
.
The overall sequence therefore goes like this:
void DoSomethingAwesome() { if (ImpersonateSelf(SecurityImpersonation)) { HANDLE threadToken; if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &threadToken)) { TOKEN_PRIVILEGES privileges; privileges.PrivilegeCount = 1; privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (LookupPrivilegeValue(nullptr, SE_TCB_NAME, &privileges.Privileges[0].Luid)) { if (AdjustTokenPrivileges(newToken, FALSE, &privileges, 0, nullptr, nullptr)) { // Now do the thing you wanna do. HANDLE newToken; if (DuplicateTokenEx(threadToken, TOKEN_ALL_ACCESS, nullptr, SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &newToken)) { DWORD sessionId = ...; if (SetTokenInformation(newToken, TokenSessionId, &sesionId, sizeof(sessionId))) { // Hooray } CloseHandle(newToken); } } } CloseHandle(threadToken); } RevertToSelf(); } }
Of course, in real life, you probably would use RAII types to ensure that handles get closed and to remember to RevertToSelf()
after a successful ImpersonateSelf()
.
The other “gotcha” with AdjustTokenPrivileges is that if the user doesn’t have the privilege(s) in question, it still reports success even though it did nothing. (This is called out in the documentation, of course, but is often overlooked.)
To determine whether the call actually succeeded, you need to call GetLastError, which will be set to zero if the call succeeded.
I assume it’s threadToken instead of newToken for AdjustTokenPrivileges, in the correct implementation?