A complete Impersonation Demo in C#.NET
October 22, 2010 7 Comments
Under some scenarios we need impersonate another Windows account and do some work under that user’s session, for example:
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 a complete impersonation process, so I decided to write one that refer as more details as I can, and actually my code was a code snippet combination 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 additional, I tried to access this folder programmatically under my account, UnauthorizedAccessException will be thrown!
- I will access TempUser’s HKEY_CURRENT_USER and do registry key creation, reading and deleting.
Code Snippet
We need 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 notes: 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
“TempKeyByWayne” was created under HKCU.
Complete source code download
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
Test comments.
In many cases, good work.I ‘Please set the return value LL.
Thanks for the reply, but what did you mean for “set the return value LL” please? I couldn’t estimate your context:)
Hello,
Nice article. Unfortunately I can’t download the file with source code. Could you add a mirror or send me a link to download it?
Thank you in advance
César
Hi Wayne,
Very good demo, is there a place where I can download the code? The sugar sync says that the file has been removed.
Howdy! I simply want to give an enormous thumbs up for the great data you’ve
here on this post. I might be coming back to your blog for extra soon.
Hi good weblog! Gentleman. Superb. Fantastic. I most certainly will book mark your web site in addition to take the nourishes also? I am just happy to discover several helpful information and facts listed here within the offered, we need produce additional methods for this regard, many thanks for revealing.