How to Impersonate
Guillermo recently started blogging about some Whidbey enhancements around impersonation. However, figuring out how to impersonate in the first place can be a little less than obvious.
WindowsIdentity contains an Impersonate method, but it doesn't accept any parameters. That means that we'll need to supply the user and password through some other means. The constructor for WindowsIdentity that takes a string looks promising, however there doesn't seem to be any way to get a password in. Instead we'll have to use the constructor that takes a token (as an IntPtr). If we intend to go that route, then we might as well just use the static WindowsIdentity.Impersonate overload that takes a token directly.
OK, so how do we get that token? Well, we can P/Invoke to LogonUser. LogonUser takes a user name, domain, and password, and returns us a token that we can use with WindowsIdentity. In addition to these parameters, LogonUser wants to know which logon provider to use (the default should suffice for most cases), and a logon type Selecting a logon type can be a tricky, so lets take a look at our options.
Logon Type(LOGON32_LOGON_xxx) |
Integer Value(From WinBase.h) | Associated Rights (From NTSecAPI.h) | Description |
BATCH | 4 | SeBatchLogonRight /SeDenyBatchLogonRight | Perform a batch logon. This is intended for servers where logon performance is vital. LogonUser will not cache credentials for a user logged in with this type. |
INTERACTIVE | 2 | SeInteractiveLogonRight /SeDenyInteractiveLogonRight | Log a user into the computer if they will be interactively using the machine (for instance if your code is going to provide them with a shell). LogonUser will cache credentials for a user logged in with this type. |
NETWORK | 3 | SeBatchNetworkRight /SeDenyNetworkLogonRight | Similar to batch logon in that this logon type is intended for servers where logon performance is important. Credentials will not be cached, and the token returned is not a primary token but an impersonation token instead. It can be converted to a primary token with DuplicateHandle. |
NETWORK_CLEARTEXT | 8 | SeBatchNetworkRight /SeDenyNetworkLogonRight | Similar to a network logon, except that the credentials are stored with the authentication package that validates the user. This logon type is only available with the WINNT50 logon provider, and on Windows 2000 or higher. |
NEW_CREDENTIALS | 9 | SeBatchNetworkRight /SeDenyNetworkLogonRight | Create a new set of credentials for network connections. (For instance runas /netonly). This logon type is only available with the WINNT50 logon provider and on Windows 2000 or higher. |
SERVICE | 5 | SeServiceLogonRight /SeDenyServiceLogonRight | Logon as a service. |
UNLOCK | 7 | Reserved for use by GINA implementations. |
One important thing to note is that in order to use WindowsIdentity.Impersonate(), we need a primary token, so if we go with LOGON32_LOGON_NETWORK, we'll need to use DuplicateHandle in order to get at the primary token. Since I like to avoid extra work like that, it looks like LOGON32_LOGON_BATCH or LOGON32_LOGON_INTERACTIVE would both be more appropriate choices. Selecting between them will depend on the rights that the account we're trying to logon has.
Once your call to LogonUser has gotten you a user token for the user you'd like to impersonate, you can then call WindowsIdentity.Impersonate() and have your thread take over the identity of the Windows user you just logged on. The code might look something like this:
// Call LogonUser to get a token for the user
IntPtr userHandle = IntPtr.Zero();
bool loggedOn = LogonUser(
user,
domain,
password,
LogonType.Interactive,
LogonProvider.Default,
out userHandle);
if(!loggedOn)
throw new Win32Exception(Marshal.GetLastWin32Error());
// Begin impersonating the user
WindowsImpersonationContext impersonationContext = WindowsIdentity.Impersonate(userHandle.Token);
DoSomeWorkWhileImpersonating();
// Clean up
CloseHandle(userHandle);
impersonationContext.Undo();
However this code has a very subtle security issue that's just waiting to bite you. Before I fix the problem tomorrow, any guesses as to what might be wrong with the code?
Update 4:35 PM: Corrected an issue that wasn't the bug I'm referring to above.
Comments
Anonymous
March 21, 2005
The first thing that comes to my mind is that you don't restore the process to its previous identity. First save the existing security context, impersonate, do priviledged work, restore back the security context to the original one.Anonymous
March 21, 2005
Shoot -- you're right, but that wasn't the bug I was looking for :-) I've just updated to code to correct the issue.
You're on the right track though. Any other guesses?
-ShawnAnonymous
March 21, 2005
Well, if the username/password supplied is more privileged than you are, and an exception occurs in DoSomeWorkWhileImpersonating, then you don't restore the original context either.
Also, when you impersonate, the impersonated user's registry hive isn't loaded (i.e. HKEY_CURRENT_USER still points to the impersonating user's hive, not the impersonatee) so that might be a problem if you try to access the registry.Anonymous
March 21, 2005
Ah, this is one of my favourites. I wrote about this issue in my blog a few months ago:
http://blogs.msdn.com/ericlippert/archive/2004/09/01/224064.aspx
If an exception is thrown in DoSomeWorkWhileImpersonating then the caller up the stack -- which might be less-privileged hostile code -- gets to handle the exception. The caller then gets its privileges elevated.
The obvious thing to do is use a finally block to ensure that the cleanup happens, but actually that is not sufficient either. The hostile caller could register an exception filter which runs BEFORE the finally block.
You've got to be SUPER CAREFUL when impersonating in managed code if your caller can be partially trusted.Anonymous
March 21, 2005
Something like this is begging for a little utility object that implements IDisposable so that you can use it with a <i>using</i> block. That way you <b>can't</b> forget to set things back.Anonymous
April 04, 2005
can someone confirm that the impersonation context is on a per thread basis and not a per process basis. thx.Anonymous
April 05, 2005
Yes, impersonation is per-thread.
-ShawnAnonymous
August 06, 2006
hi
i do impersonation according to Microsoft Recommend .
i use impersonation to access a file that it's
Folder have Acls permission for one user.
the impersonation is ok and impersonate anonymous to user that have permission to access the file ,but again a popoup appear
to enter username and password eo view file.
my code is :
Dim LOGON32_LOGON_INTERACTIVE As Integer = 2
Dim LOGON32_PROVIDER_DEFAULT As Integer = 0
Dim impersonationContext As WindowsImpersonationContext
Declare Function LogonUserA Lib "advapi32.dll" (ByVal lpszUsername As String, _
ByVal lpszDomain As String, _
ByVal lpszPassword As String, _
ByVal dwLogonType As Integer, _
ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Integer
Declare Auto Function DuplicateToken Lib "advapi32.dll" ( _
ByVal ExistingTokenHandle As IntPtr, _
ByVal ImpersonationLevel As Integer, _
ByRef DuplicateTokenHandle As IntPtr) As Integer
Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long
Public Sub Page_Load(ByVal s As Object, ByVal e As EventArgs)
If impersonateValidUser("Doroudian", "ws9", "123") Then
response.write("ok")
image1.imageurl="http://ws9/Docs/36.jpg"
'Insert your code that runs under the security context of a specific user here.
'undoImpersonation()
Else
'Your impersonation failed. Therefore, include a fail-safe mechanism here.
End If
End Sub
Private Function impersonateValidUser(ByVal userName As String, _
ByVal domain As String, ByVal password As String) As Boolean
Dim tempWindowsIdentity As WindowsIdentity
Dim token As IntPtr = IntPtr.Zero
Dim tokenDuplicate As IntPtr = IntPtr.Zero
Dim princs As GenericPrincipal
Dim rAdmin(0) As String
impersonateValidUser = False
If RevertToSelf() Then
If LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
If DuplicateToken(token, 2, tokenDuplicate) <> 0 Then
tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
impersonationContext = tempWindowsIdentity.Impersonate()
If Not impersonationContext Is Nothing Then
impersonateValidUser = True
End If
End If
End If
End If
End Function
Many thanks for any guides
Regard
AliAnonymous
October 08, 2006
(I am a newbie in VB 2005)I tried the code from Ali, however I cannot get it work. I did run it with ClientPC1LocalUser and tried to stop a service by WMI on RemotePC1 with RemotePC1User1.It always result in Login failed.What is wrong? I have too less knowledge to find the error.Thanks in advanceKlausAnonymous
October 31, 2006
So, can you tell me which friendly security policy name is associated with SeBatchNetworkRight?Anonymous
November 06, 2006
How does this work across active directory, or does it? I have a user in active directory that I need to impersonate in Sharepoint, but cannot seem to use LogonUser for this.Anonymous
May 22, 2007
PingBack from http://blogtips.blog.bizhat.com/2007/05/23/how-to-blog-89/Anonymous
March 15, 2009
PingBack from http://www.aghausman.net/dotnet/saving-and-retrieving-file-using-filestream-sql-server-2008.htmlAnonymous
March 15, 2009
FileStream data type is a very important feature of SQL Server 2008 and gradually getting popular amongst