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:
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
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.
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.)
As a temporary workaround, you can call RevertToSelf to return the thread to the security context of the process token before calling LogonUser.
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
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:
6: ' impersonate the Windows Authenticated User
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
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
PingBack from http://www.artofbam.com/wordpress/?p=3928
To identify the problem one can also use procmon from sysinternals or set auditing in the accessed objects, if the "object" is SQL Server then SQL profiler would show the actual account that accesses it. You may find these helpful next time t-shooting identity flow challenges :).
http://blogs.msdn.com/alikl/archive/2007/04/11/authentication-hub.aspx
procmon and auditing is demonstrated here
http://blogs.msdn.com/alikl/archive/2007/04/03/who-access-my-file.aspx
http://blogs.msdn.com/alikl/archive/2007/04/01/file-access-auditing-i-am-not-afraid-of-gpo.aspx
Yep, we actually used procmon at the beginning, when one of the assumptions we were making was the job manager account was missing special permission needed to access core ASP.NET files and folders, but we didn't get any "Access denied" there...
In fact the problem was not accessing a file or folder but rather we were failing changing the account for the specific thread, not for the entire process, and procmon just shows me NETWORK SERVICE which is the account running the application pool.
There are circumstances where taking a dump is not possible or simply not convenient; imagine a situation