Uncategorized

Remember to undo your impersonation

A couple of weeks ago I got an interesting query from a customer, whom had a problem impersonating a service user account by code; the design was a bit more complicated, though:

    • Impersonation not set in web.config
    • By code they needed to impersonate the account logged on the client issuing the HTTP request (this worked fine)
    • By code they needed to impersonate a service account they used to access a backend database (here they were getting an access denied error)
    • Switch back to the previous user, the one logged on the client (again this was working fine)

This was quite clearly an impersonation problem, and after some debugging we found the “Access Denied” was being thrown when executing the line highlighted in red in the following snippet, way before even trying to access the network to read the backend database:

 1: If CType(LogonUser(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token), Boolean) Then
 2:    If DuplicateToken(token, 2, tokenDuplicate) Then
 3:    Dim identity As New WindowsIdentity(tokenDuplicate)
 4:       If System.Web.HttpContext.Current Is Nothing Then
 5:          Dim mImpersonatedContext As WindowsImpersonationContext = identity.Impersonate
 6:       Else
 7:          System.Web.HttpContext.Current.Items("ImpersonationContext") = identity.Impersonate
 8:       End If
 9:    End If
 10: [...]

In the screenshot below you can see the “Access Denied” message when trying to display the WindowsIdentity.Name property

autos
autos

Since I was able to repro on my machine, I attached WinDbg to the worker process and set a breakpoint on advapi32!ImpersonateLoggedOnUser and having a look at the stack and managed exceptions the problem was quite clear. !gle shows the last error for the current thread:

   1: 0:017> !gle
   2: LastErrorValue: (Win32) 0x5 (5) - Access is denied.
   3: LastStatusValue: (NTSTATUS) 0xc0000022 - {Access Denied}  A process has requested access to an object,
   4:     but has not been granted those access rights.

Also confirmed by the managed exceptions:

   1: Exception object: 021314ec
   2: Exception type: System.Web.HttpException
   3: Message: An error occurred while attempting to impersonate.  Execution of this request cannot continue.
   4: InnerException: <none>
   5: StackTrace (generated):
   6:     SP       IP       Function
   7:     01ECF4F0 044DCB73 System.Web.ImpersonationContext.GetCurrentToken()
   8:     01ECF534 041E3BE9 System.Web.ImpersonationContext.get_CurrentThreadTokenExists()
   9:     01ECF564 0417FB4E System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
  10:     01ECF61C 041922CC System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)
  11:     01ECF66C 0417EEA6 System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
  12:     01ECF688 04183DB5 System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
  13:
  14:
  15: Exception object: 021312a8
  16: Exception type: System.Security.SecurityException
  17: Message: Access is denied.
  18:
  19: InnerException: <none>
  20: StackTrace (generated):
  21:     SP       IP       Function
  22:     01ECF24C 79636928 System.Security.Principal.WindowsIdentity.GetCurrentInternal(System.Security.Principal.TokenAccessLevels, Boolean)
  23:     01ECF26C 79389652 System.Security.Principal.WindowsIdentity.GetCurrent()
  24:     01ECF278 06350A92 WebApplication1._Default.Page_Load(System.Object, System.EventArgs)
  25:     01ECF318 04301954 System.Web.UI.Control.OnLoad(System.EventArgs)
  26:     01ECF328 043019A0 System.Web.UI.Control.LoadRecursive()
  27:     01ECF33C 043147C4 System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
  28:     01ECF50C 04312982 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
  29:     01ECF544 0431285F System.Web.UI.Page.ProcessRequest()
  30:     01ECF57C 0431277F System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
  31:     01ECF584 04312712 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
  32:     01ECF598 063502F6 ASP.default_aspx.ProcessRequest(System.Web.HttpContext)
  33:     01ECF5A4 041BA93F System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
  34:     01ECF5DC 0417FAD1 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)

As a test we added the Job Manager account to the local Administrators group and the problem went away, so this was clearly a lack of permission for that account; specifically, the Job Manager user was not allowed to “take ownership” of the current thread, which was already impersonating the client account whom issued the HTTP request. Since we where using service account we checked the permission required in the article Process and request identity in ASP.NET, in particular the ASPNET account specific permission configurable through the Group Policy snap-in (gpedit.msc); we added the required permission, but the problem was still there.

We then found a quite old (but still applicable) KB article which seemed to be applicable to this problem: LogonUser fails in ISAPI extensions. Here’s the interesting part:

CAUSE

The code inside LogonUser tries to open the process token. It fails since the authenticated user may not have access to the process token (SYSTEM if it’s an inproc ISAPI.)

RESOLUTION

As a temporary workaround, you can call RevertToSelf to return the thread to the security context of the process token before calling LogonUser.

STATUS

This behavior is by design.

So, going back in the stack where the failing call started, here is what we find:

   1: Dim test As New customer
   2: Dim col As List(Of CustomerDetails)
   3: Dim p As WindowsPrincipal = HttpContext.Current.User
   4: Dim id As WindowsIdentity = p.Identity
   5:
   6: 'impersonate the Windows Authenticated User
   7: Dim wic As WindowsImpersonationContext = id.Impersonate()
   8: col = test.getcustomers() ' Works okay to access database
   9:
  10: 'Error Switching below, step into code
  11: CBPUser.SwitchToIOUser()   ' Access Denied, step through code to see issue arise.
  12: col = test.getcustomers()  ' Fails to access database
  13:
  14: CBPUser.SwitchFromIOUser() ' Switch back
  15: col = test.getcustomers()  ' Works okay to access database again

I then had a look at the MSDN docs about impersonation, especially to find some sample code, like for example How To: Using impersonation and delegation in ASP.NET 2.0 and How to implement impersonation in an ASP.NET application: interesting enough all the samples in those articles always revert the impersonation calling the WindowsImpersonationContext.Undo() method (which under the covers ultimately calls RevertToSelf(), as you can guess)…

Since testing the code in practice is easier an quicker, I added the Undo() call and run it again:

   1: Dim test As New customer
   2: Dim col As List(Of CustomerDetails)
   3: Dim p As WindowsPrincipal = HttpContext.Current.User
   4: Dim id As WindowsIdentity = p.Identity
   5:
   6: ' impersonate the Windows Authenticated User
   7: Dim wic As WindowsImpersonationContext = id.Impersonate()
   8: col = test.getcustomers() ' Works okay to access database
   9: wic.Undo()
  10:
  11: ' Error Switching below, step into code
  12: CBPUser.SwitchToIOUser()   'Works fine now, no more access denied!
  13: col = test.getcustomers()  'Works ok to access database
  14:
  15: CBPUser.SwitchFromIOUser() ' Switch back
  16: col = test.getcustomers()  ' Works okay to access database again

Much better! ? Just to be sure, Sql Profiler shown a connection with the service account which was our final goal. So the final message is: remember to always use Undo() when you’re done with your code impersonation.

Case closed Sherlock! ?

Carlo

Quote of the Day:

Resentment is like taking poison and hoping the other person dies.

–St. Augustine

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.