A Complete Impersonation Demo in C#.NET
A complete impersonation demo in C#.NET
Under some scenarios, we need impersonate another Windows account and do some work under that user’s session, for example:
- An enterprise ASP.NET web application provides server administrators’ ability to access the server under some specific privilege set; Server admin input their NT account information (domain\account + password) on the page, we need get WinNT Access Token and then impersonate this server user, so that we acquire its specific privilege and do the things ONLY THIS ACCOUNT CAN DO.
- We developed a Windows Service which needs internet access periodically, but a specific user sets an Sock5 proxy to access the internet, then your Windows Service needs to know the Socks proxy information so that it could access internet, you must impersonate this user and read the settings.
Impersonation Definition
Definition copied from http://msdn.microsoft.com/en-us/library/aa376391(VS.85).aspx:
Impersonation is the ability of a thread to execute using different security information than the process that owns the thread. Typically, a thread in a server application impersonates a client. This allows the server thread to act on behalf of that client to access objects on the server or validate access to the client’s own objects.
I read many articles and blogs and wrote an ImpersonateHelper
class to do impersonation work, during the investigating, I noticed that very few articles/blogs refer to a complete impersonation process, so I decided to write one that refers as many details as I can, and actually my code was a code snippet combination that came from 10+ sources.
Functionality
I create a local user: TempUser
which belongs to “Administrators” (make sure log on TempUser
at least once), I logged on as my own account and I am going to impersonate TempUser
and do two things:
- Create a folder “C:\TempFolder”, modify its default privilege, ONLY
TempUser
has full control of it, I will create a text file under this folder after impersonating to prove impersonation is successfully.Notes: After setting
TempUser
as the only owner, my current account cannot access this folder except privilege promotion (I disabled UAC, if UAC is enabled, a prompt window will pop up and as for Admin confirm).In addition, I tried to access this folder programmatically under my account,
UnauthorizedAccessException
will be thrown! - I will access
TempUser
’sHKEY_CURRENT_USER
and do registry key creation, reading and deleting.
Code Snippet
We need to invoke three very famous Win32 API: LogonUser, DuplicateToken and RevertToSelf.
[DllImport("advapi32.dll")]
public static extern int LogonUser(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken
(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
///
/// A process should call the RevertToSelf function after finishing
/// any impersonation begun by using the DdeImpersonateClient,
/// ImpersonateDdeClientWindow, ImpersonateLoggedOnUser,
/// ImpersonateNamedPipeClient, ImpersonateSelf,
/// ImpersonateAnonymousToken or SetThreadToken function.
/// If RevertToSelf fails, your application continues to run
/// in the context of the client, which is not appropriate.
/// You should shut down the process if RevertToSelf fails.
/// RevertToSelf Function: http://msdn.microsoft.com/en-us/library/aa379317(VS.85).aspx
///
/// A boolean value indicates the function succeeded or not.
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
An important note: In order to access HKCU, we need invoke another Win32 API: LoadUserProfile, to acquire the handle of HKCU under TempUser, code below, as I highlighted line 45 and 49, after invoking LoadUserProfile
, hProfile
will be set handle to HKCU
:
[StructLayout(LayoutKind.Sequential)]
public struct ProfileInfo
{
///
/// Specifies the size of the structure, in bytes.
///
public int dwSize;
///
/// This member can be one of the following flags: PI_NOUI or PI_APPLYPOLICY
///
public int dwFlags;
///
/// Pointer to the name of the user.
/// This member is used as the base name of the directory in which to store a new profile.
///
public string lpUserName;
///
/// Pointer to the roaming user profile path.
/// If the user does not have a roaming profile, this member can be NULL.
///
public string lpProfilePath;
///
/// Pointer to the default user profile path. This member can be NULL.
///
public string lpDefaultPath;
///
/// Pointer to the name of the validating domain controller, in NetBIOS format.
/// If this member is NULL, the Windows NT 4.0-style policy will not be applied.
///
public string lpServerName;
///
/// Pointer to the path of the Windows NT 4.0-style policy file. This member can be NULL.
///
public string lpPolicyPath;
///
/// Handle to the HKEY_CURRENT_USER registry key.
///
public IntPtr hProfile;
}
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);
[DllImport("Userenv.dll",
CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool UnloadUserProfile(IntPtr hToken, IntPtr lpProfileInfo);
Code to Execute Impersonation
WindowsIdentity m_ImpersonatedUser;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
const int SecurityImpersonation = 2;
const int TokenType = 1;
try
{
if (RevertToSelf())
{
Console.WriteLine("Before impersonation: " +
WindowsIdentity.GetCurrent().Name);
String userName = "TempUser";
IntPtr password = GetPassword();
if (LogonUser(userName, Environment.MachineName,
"!@#$QWERasdf", LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, SecurityImpersonation, ref tokenDuplicate) != 0)
{
m_ImpersonatedUser = new WindowsIdentity(tokenDuplicate);
using (m_ImpersonationContext = m_ImpersonatedUser.Impersonate())
{
if (m_ImpersonationContext != null)
{
Console.WriteLine("After Impersonation succeeded:
" + Environment.NewLine +
"User Name: " +
WindowsIdentity.GetCurrent
(TokenAccessLevels.MaximumAllowed).Name +
Environment.NewLine +
"SID: " +
WindowsIdentity.GetCurrent
(TokenAccessLevels.MaximumAllowed).User.
Value);
#region LoadUserProfile
// Load user profile
ProfileInfo profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = userName;
profileInfo.dwFlags = 1;
Boolean loadSuccess =
LoadUserProfile(tokenDuplicate, ref profileInfo);
if (!loadSuccess)
{
Console.WriteLine("LoadUserProfile()
failed with error code: " +
Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (profileInfo.hProfile == IntPtr.Zero)
{
Console.WriteLine(
"LoadUserProfile() failed -
HKCU handle was not loaded. Error code: " +
Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
#endregion
CloseHandle(token);
CloseHandle(tokenDuplicate);
// Do tasks after impersonating successfully
AccessFileSystem();
// Access HKCU after loading user's profile
AccessHkcuRegistry(profileInfo.hProfile);
// Unload user profile
// MSDN remarks http://msdn.microsoft.com/en-us/library/bb762282
// (VS.85).aspx
// Before calling UnloadUserProfile you should ensure
// that all handles to keys that you have opened in the
// user's registry hive are closed. If you do not
// close all open registry handles, the user's profile fails
// to unload. For more information,
// see Registry Key Security and Access Rights and Registry Hives.
UnloadUserProfile(tokenDuplicate, profileInfo.hProfile);
// Undo impersonation
m_ImpersonationContext.Undo();
}
}
}
else
{
Console.WriteLine("DuplicateToken()
failed with error code: " + Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
catch (Win32Exception we)
{
throw we;
}
catch
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (token != IntPtr.Zero) CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero) CloseHandle(tokenDuplicate);
Console.WriteLine("After finished impersonation: " +
WindowsIdentity.GetCurrent().Name);
}
AccessFileSystem Method
private static void AccessFileSystem()
{
// Access file system %appdata% will be "C:\Users\TempUser\appdata\Roaming"
String appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
File.AppendAllText("C:\\TempFolder\\Temp.txt", "some text...");
}
AccessHkcuRegistry Method
private static void AccessHkcuRegistry(IntPtr hkcuHandle)
{
// Access registry HKCU
using (SafeRegistryHandle safeHandle = new SafeRegistryHandle(hkcuHandle, true))
{
using (RegistryKey tempUserHKCU = RegistryKey.FromHandle(safeHandle))
{
// Unum all sub keys under tempuser's HKCU
String[] keys = tempUserHKCU.GetSubKeyNames();
// Create a new sub key under tempuser's HKCU
using (RegistryKey tempKeyByWayne = tempUserHKCU.CreateSubKey("TempKeyByWayne"))
{
// Ensure priviledge
//RegistrySecurity registrySecurity = new RegistrySecurity();
//RegistryAccessRule accessRule =
// new RegistryAccessRule(Environment.MachineName +
// "\\" + userName,
// RegistryRights.TakeOwnership,
// InheritanceFlags.ContainerInherit,
// PropagationFlags.None,
// AccessControlType.Allow);
//registrySecurity.SetAccessRule(accessRule);
//tempKeyByWayne.SetAccessControl(registrySecurity);
// Create a new String value under created TempKeyByWayne subkey
tempKeyByWayne.SetValue("StrType", "TempContent", RegistryValueKind.String);
// Read the value
using (RegistryKey regKey = tempUserHKCU.OpenSubKey("TempKeyByWayne"))
{
String valueContent = regKey.GetValue("StrType") as String;
Console.WriteLine(valueContent);
}
// Delete created TempKeyByWayne subkey
tempUserHKCU.DeleteSubKey("TempKeyByWayne");
tempKeyByWayne.Close();
}
}
}
}
Impersonation Result and Verification
Temp.txt was created.
“TempKeyByWayne
” was created under HKCU.
References
- How to implement impersonation in an ASP.NET application
http://support.microsoft.com/kb/306158 - LogonUser Win32 API
http://msdn.microsoft.com/en-us/library/aa378184(VS.85).aspx - How to spawn a process that runs under the context of the impersonated user in Microsoft ASP.NET pages
http://support.microsoft.com/kb/889251 - ASP.NET Impersonation
http://msdn.microsoft.com/en-us/library/xh507fc5(v=VS.100).aspx - Safely Impersonating Another User
http://blogs.msdn.com/b/shawnfa/archive/2005/03/22/400749.aspx - Original post permalink
http://wayneye.com/Blog/DotNet-Impersonation-Demo
Filed under: Win32 API, Windows Development, Windows Service
Tagged: .NET, C#, CodeProject, Impersonate, PInvoke, Win32 API