Some thoughts on Impersonation
I normally talk about SharePoint topics but this post is also important for non-SharePoint people. First let me explain what is the relationship between impersonation and SharePoint, or better, the relationship between SharePoint and the thread [security] token. SPRequest is the unmanaged low level class in SharePoint that works as HtppHandler for SharePoint requests. In the very heart of the security checks in SPRequest, it tries to get the SID of the user being impersonated in the current thread (Microsoft.SharePoint.Library.SPRequestInternalClass.OpenWebInternal() will be the last managed stack entry followed by some variation of Microsoft.SharePoint.Library.SPRequest when an error occurs). In some occasions, the thread token cannot be retrieved and you will see an error like System.Security.Authentication.AuthenticationException. This situation is normally related to custom code when there are other levels of Http Handlers involved, however it is potentially possible to happen in other situations which I am not aware of.
I have faced such problem and being in a production environment and not reproducible outside that environment, I was unable to do a live debug of the problem. I’ve got the iDNA trace which is the closest you can get to a live debug in a situation like that. I could confirm the very point where the application was failing (and the last point of failure was indeed in SharePoint as most errors end up in kernel.dll even though the problem is not in kernel.dll). In normal circumstances the thread token can be retrieved. The problem always happens when the custom application is involved (and the custom http handlers are in the way of SharePoint Http Handler). To make things even worse the custom application was not failing in other environments. All indications led to problems in the authentication mechanism of the environment (read this as Active Directory Problems). In order to create some tests that would help identify the problem in the environment I put together an application to test impersonation of threads and “logon as” kind of behavior. I am sure this could be done in .NET but I decided to use C++ instead because it showed to be easier to implement (and reuse some available code).
The source code and installation binaries can be found here (source) and here (installer).
Running in elevated privilege (not necessary but since I implemented I kept the code)
When you start an application there is no way you can change the privileges of that instance, you will have to run it again as Administrator to make it work. If you verify how Task Manager works in Vista and above you will notice that seeing process for all users (which requires run in elevated privileges) requires a restart in Task Manager.
This is the code that implement it:
void CImpersonationDlg::OnBnClickedButton4()
{
// TODO: Add your control notification handler code here
SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
int argc;
PWSTR *argsv = CommandLineToArgvW(GetCommandLine(), &argc);
if(argc == 0)
{
PrintError(L"Error retrieving path. Elevation was aborted");
return;
}
// Get Process Full Path
sei.lpFile = (LPCWSTR)argsv[0];
// Ask for privileges elevation.
sei.lpVerb = _T("runas");
sei.nShow = SW_SHOWNORMAL;
bool closeWindow = true;
if(!ShellExecuteEx(&sei))
{
PrintError(L"Unable to elevate privileges");
closeWindow = false;
}
HeapFree(GetProcessHeap(), 0, argsv);
if(closeWindow)
{
this->CloseWindow();
}
}
Getting the Thread Token
In my test I get the same error that the custom application was getting (it is normal to get the error when you do not impersonate).
This is the code for that:
void CImpersonationDlg::OnBnClickedButtonThreadget()
{
// TODO: Add your control notification handler code here
HANDLE tokenHandle;
ClearFields();
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &tokenHandle))
{
PrintError(L"Unable to get Current Thread Token. Try to impersonate first.");
return;
}
AfxMessageBox(L"Current Thread Token retrieved successfully");
FillUserInformationFromToken(tokenHandle);
CloseHandle(tokenHandle);
}
And the code to show the latest system error formatted:
// Print System Error adapted from https://msdn.microsoft.com/en-us/library/ms680582(VS.85).aspx
void CImpersonationDlg::PrintError(LPTSTR lpszFunction)
{
// Retrieve the system error message for the last-error code
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s\nFailed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox((LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
Getting Process Token
Process Token must exist. You may not have rights to see it.
This is the code:
void CImpersonationDlg::OnBnClickedButtonProcessget()
{
// TODO: Add your control notification handler code here
HANDLE tokenHandle;
ClearFields();
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tokenHandle))
{
PrintError(L"Unable to get Current Process Token");
return;
} else
FillUserInformationFromToken(tokenHandle);
AfxMessageBox(L"Process Token was retrieved successfully");
if(tokenHandle)
CloseHandle(tokenHandle);
}
Code for getting user information from the token:
void CImpersonationDlg::FillUserInformationFromToken(HANDLE Token)
{
TCHAR user[MAX_PATH], domain[MAX_PATH];
DWORD sizeSID;
DWORD sizeUser = sizeof(user);
DWORD sizeDomain = sizeof(domain);
SID_NAME_USE sidNameUse;
TOKEN_USER* tokenUser = (TOKEN_USER*)malloc(MAX_PATH);
TCHAR* sidString;
if(!GetTokenInformation(Token, TokenUser, tokenUser, MAX_PATH, &sizeSID))
{
PrintError(L"Unable to retrieve user info.");
return;
} else
{
ConvertSidToStringSid(tokenUser->User.Sid, &sidString);
textSID.SetWindowText(sidString);
LocalFree(sidString);
}
if(!LookupAccountSid(NULL, tokenUser->User.Sid, user, &sizeUser, domain, &sizeDomain, &sidNameUse))
{
PrintError(L"Unable to retrieve user infomation (UserName and Domain). Only SID was retrieved");
} else
{
textDomain.SetWindowText(domain);
textUserName.SetWindowText(user);
//textPassword.SetWindowText(L"nopassword");
}
}
Impersonating the process user (button Impersonate Self)
After doing this you can retrieve the thread token.
Code:
void CImpersonationDlg::OnBnClickedButtonImpersonate()
{
// TODO: Add your control notification handler code here
HANDLE tokenHandle;
ClearFields();
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tokenHandle))
{
PrintError(L"Unable to get Current Process Token");
return;
}
if(!ImpersonateLoggedOnUser(tokenHandle))
{
PrintError(L"Unable to impersonate credential. Try 'Interactive Logon' instead.");
} else
{
AfxMessageBox(L"Impersonation from Process Token was sucessful");
btnRevertToSelf.EnableWindow();
ClearFields();
FillUserInformationFromToken(tokenHandle);
}
if(tokenHandle)
CloseHandle(tokenHandle);
}
Impersonating User you typed in
In this option you can choose whether to Logon as Network token or Logon as a user. If the user has no logon rights in the computer the attempt will fail (and you would probably would get no error from NETWORK logon). Logging on as network will not enable you to access both Process and Thread tokens (“Revert to Self” will resolve the problem in this case).
Code:
void CImpersonationDlg::OnBnClickedButtonProcessset()
{
// TODO: Add your control notification handler code here
HANDLE token;
TCHAR userName[MAX_PATH], domain[MAX_PATH], password[MAX_PATH];
textUserName.GetWindowText(userName, sizeof(userName));
textDomain.GetWindowText(domain, sizeof(domain));
textPassword.GetWindowText(password, sizeof(password));
textSID.SetWindowText(L"");
DWORD logonType = LOGON32_LOGON_NETWORK;
if(chkInteractive.GetCheck() == BST_CHECKED)
logonType = LOGON32_LOGON_INTERACTIVE;
if(!LogonUser(userName, domain, password, logonType, LOGON32_PROVIDER_DEFAULT, &token))
{
PrintError(L"Unable to logon with user credential.");
} else
{
if(!ImpersonateLoggedOnUser(token))
{
PrintError(L"Unable to impersonate credential.");
} else
{
AfxMessageBox(L"Impersonation was sucessful. You may want to Impersonate Self now.");
btnRevertToSelf.EnableWindow();
ClearFields();
FillUserInformationFromToken(token);
}
}
if(token)
CloseHandle(token);
}
Reverting to Self
Returns to the user that initiated the application.
Code:
void CImpersonationDlg::OnBnClickedReverttoself()
{
// TODO: Add your control notification handler code here
// TODO: Add your control notification handler code here
if(!RevertToSelf())
{
PrintError(L"Unable to revert to self.");
} else
{
AfxMessageBox(L"Revert to Self was successful.");
}
}