Parallel.ForEach() changes Impersonation Context

2019-03-19 11:51发布

问题:

Today we deployed our newly created ASP.NET application to the server and soon we realized there was a strange security-related issue which was causing the application to crash. This is an internal application and we use Impersonation to manage how the users access resources. The application however, throws an "Access Denied" exception when the user attemps to access a folder over which they have full control.

The exception was in fact an AggregateException and was being thrown in a method which uses Parallel.ForEach to enumerate over a list and inside the body, it attempts to access the folder, but at this point the Impersonation Context gets changed and the worker thread runs as the application pool's identity, which doesn't have access to the folder hence the exception.

To confirm this, I looked at the process identity before and inside the body of Parallel.ForEach:

string before = WindowsIdentity.GetCurrent().Name;
Debug.WriteLine("Before Loop: {0}", before);

Parallel.ForEach(myList, currentItem =>
{
    string inside = WindowsIdentity.GetCurrent().Name;
    Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
});

When I run the app, this is what gets printed out:

Before Loop: MyDomain\ImpersonatedUser

Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 8)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 9)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 10)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)

As you can see, some threads are running as the impersonated identity and some as the application pool (in this case, LocalSystem) and there doesn't seem to be a pattern. The previous frame in the Call Stack window also goes to the unmanaged kernel32.dll, which makes me think CLR isn't validating the context before delegating it to the OS.

Any idea why this is happening? Is that a known issue/bug?

回答1:

Unlike the Task class, Parallel doesn't seem to be capturing the ExecutionContext you're currently running on (which in turns captures the SecurityContext which holds the WindowsIdentity). It uses the one avaliable inside the current Thread.

You have to explicitly capture the desired context:

IntPtr token = WindowsIdentity.GetCurrent().Token;

Parallel.ForEach(myList, currentItem =>
{
   using (WindowsIdentity.Impersonate(token))
   {
      string inside = WindowsIdentity.GetCurrent().Name;
      Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
   }
});


回答2:

The whole Impersonation concept in Windows is a per thread concept. You see, when a new thread is created, it inherits the process' privileges token. But threads, unlike processes, also have an impersonation token. When you call WindowsIdentity.Impersonate(token) you set the impersonation token on the calling thread.

Also when you call Impersonate you set the thread's primary token pointer to point to the impersonation token instead of the process' primary token, which is the default.

A little bit more understanding and knowledge in the WinAPI would lead you to know that what happened in your service is the expected behavior.