Dela via


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 LogonUserLogonUser 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?

    -Shawn

  • Anonymous
    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.

    -Shawn

  • Anonymous
    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
    Ali

  • Anonymous
    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 advanceKlaus

  • Anonymous
    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.html

  • Anonymous
    March 15, 2009
    FileStream data type is a very important feature of SQL Server 2008 and gradually getting popular amongst