3

I am dealing with a nasty issue that has me ready to tear my hair out. I have a C# console application that uses Microsoft's HttpListener class to listen for web requests. The idea is that the console application runs in the background as UserAccountA (low priv). UserAccountB (Administrator, etc) comes along, accesses a webpage through the listener, and has his or her identity impersonated. Same kind of thing IIS or WCF does. I believe I had this working on Windows 7, but now I'm on Windows 8.1 and it's failing again. Maybe I never had it to begin with, or perhaps this is a new twist.

If I run the program through Visual Studio, I can access it with Internet Explorer 11. For some reason, it asks me to type in my local credentials. I'm assuming that's something to do with the out of the box behavior of IE11. Once I type it in, it accepts it. My code starts like this:

    protected virtual void Listen(object o)
    {
        HttpListener h = (HttpListener)o;

        while (h.IsListening && (this.Disposed == false))
        {
            IAsyncResult Result = null;

            Result = h.BeginGetContext(new AsyncCallback(this.ListenerCallback), h);

            Result.AsyncWaitHandle.WaitOne();
        }
    }

And is picked up like this (abbreviated):

    protected virtual void ListenerCallback(IAsyncResult Result)
    {
        HttpListener h = (HttpListener)Result.AsyncState;
        HttpListenerContext context = null;
        System.Security.Principal.WindowsIdentity identity = null;

        context = h.EndGetContext(Result);

        identity = (System.Security.Principal.WindowsIdentity)context.User.Identity;

        using (System.Security.Principal.WindowsImpersonationContext wic = identity.Impersonate())
        {
             //method call to process request, under impersonation
        }
    }

I've stepped through this code when using two different accounts (one to host, one to access). I've verified using this statement:

   system.security.principal.windowsidentity.getcurrent().name

That the current identity DOES transition. The listener is configured to start up with NTLM AuthenticationScheme.

I suppose the complicated part is in the method that gets invoked during impersonation. We're doing a bit of abstraction, but let me just point at the part that fails. In this case, we are connecting to SQL server using SSPI - so the entire point of the impersonation is to grab the client's existing logon and gracefully get them into SQL Server. Again, right now, this is all local to one computer. it is not even in an active directory domain. Everything is local, with local accounts. It starts like this:

        System.Data.Common.DbProviderFactory fact = null;

        if (UseSql())
        {
            fact = System.Data.Common.DbProviderFactories.GetFactory("System.Data.SqlClient");
        }

No big deal. We use the provider factory in another project constantly with no issues (but that's just a regular WPF app, nothing like this). Right after this is where we'd set up the SQL login string with the SSPI attribute. But we don't even get that far. This is exactly where it fails, with this message:

A first chance exception of type 'System.IO.FileLoadException' occurred in mscorlib.dll

Additional information: Could not load file or assembly 'System.Data.OracleClient, Version=4.0.0.0, Culture=neutral, > PublicKeyToken=b77a5c561934e089' or one of its dependencies. Either a required impersonation level was not provided, or the provided impersonation level is invalid. (Exception from HRESULT: 0x80070542)

http://i854.photobucket.com/albums/ab103/srVincentVega/error7_zpsmb1xqrp0.png http://i854.photobucket.com/albums/ab103/srVincentVega/error7_zpsmb1xqrp0.png

We're not using Oracle at all, I'm assuming that's just the first thing it barfs on when it goes and tries to open up the DbProviderFactory. To me, the key is that impersonation level thing. In this particular experiment, both accounts (A & B) are local administrators. I've ensured via local Security Policy that they can impersonate accounts after login. And if I look at my event log, the impersonation DOES seem to be working...

Special privileges assigned to new logon.

Subject: Security ID:

pc1\userA

Account Name: userA

Account Domain: pc1 Logon ID: 0xC4D0F3F

Privileges:

SeSecurityPrivilege

SeTakeOwnershipPrivilege

SeLoadDriverPrivilege

SeBackupPrivilege

SeRestorePrivilege

SeDebugPrivilege

SeSystemEnvironmentPrivilege

SeImpersonatePrivilege

I've tried so many variations here that I'm starting to hinder my own analysis. I'm basically back to square one and don't know how to approach this. Does anyone have any advice or tips? This seems like it should be a very simple thing to do. I am not sure why I am having so much trouble. Maybe I just need to toss out the use of the DbProviderFactory and go with OleDb or whatever. But I have not confirmed that I can load ANY libraries at run time after impersonation. Maybe that's something to do. However, I would be very grateful for any help.

Thanks,

John

One last thing, here are reproduction steps:

Create a secondary user on your computer. Log in to create the profile. Log out and log back in as usual. Run Visual Studio as that secondary user (there are a few ways to do this, shift + right click on the VS icon), and within, make a C# console application with this skeleton:

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (HttpListener h = new HttpListener())
                {
                    h.Prefixes.Add("http://+:8090/");

                    h.AuthenticationSchemes = AuthenticationSchemes.Ntlm;

                    h.Start();

                    Console.WriteLine("Running");

                    HttpListenerContext context = h.GetContext();

                    System.Security.Principal.WindowsIdentity identity = null;
                    identity = (System.Security.Principal.WindowsIdentity)context.User.Identity;

                    using (System.Security.Principal.WindowsImpersonationContext wic = identity.Impersonate())
                    {
                        System.Data.Common.DbProviderFactory fact = null;
                        fact = System.Data.Common.DbProviderFactories.GetFactory("System.Data.SqlClient");
                    }

                    Console.ReadLine();

                    h.Stop();
                }
            }
        }
    }

Debug/Run the console app. Just call up Internet Explorer (vanilla, using your primary account) and access that URL (http://machinename:8090). The exception occurs

enter image description here http://i854.photobucket.com/albums/ab103/srVincentVega/error8_zpsilkay0bl.png

John Doh
  • 249
  • 6
  • 15
  • 1
    Depending on how you do this, you may need to add a local rule to listen a port like 8090. You can do this at an elevated command prompt: **netsh http add urlacl url=http://+:8090/ user=(SecondaryAcctName)** – John Doh Feb 19 '15 at 02:31

1 Answers1

4

Well, I read like half-way through and stopped at the exception you are getting:

A first chance exception of type 'System.IO.FileLoadException' occurred in mscorlib.dll 

The problem here is that the account that you are using to run the service has limited access to load .Net framework core libraries before it can even perform impersonation. Usually such an account which is used to host a service has admin privileges on the node where the service is hosted.

Also to allow impersonation you need to give the service account the privileges to do so using the local security policies - Local Policies - User Rights Assignment:

Act as part of the operating system

This user right allows a process to impersonate any user without authentication. The process can therefore gain access to the same local resources as that user.

Impersonate a client after authentication

Assigning this privilege to a user allows programs running on behalf of that user to impersonate a client.

Also note if you use kerberos instead of NTLM you would need to setup SPN's as well as delegation targets in AD (Actice Directory).

I don't know if this will assist you but these are things we ran into doing impersonation/delegation.

Oh yeah, impersonation level must be set to delegation if you perform more than one hop to perform impersonation.

  • Thank you for the ideas. Both of my test accounts are local admins, with the two SecPol features you mentioned turned on. I logged out and back in just to make sure they took effect. Problem persists. If I run the console app normally, and do a run-as on internet explorer as the other user, it works. If I reverse this, and do run-as the console app , and run IE normally, then it fails. I logged out and back in as the other account and the same thing happens. It only occurs when the the console app is being "run as" that this happens – John Doh Feb 19 '15 at 14:44
  • I may have introduced a red herring here. I built the full application and set it to run as a windows scheduled task as "User B." So that's running in the background. When I connect to it with Internet Explorer, it works fine. I'm not getting the error. I verified that impersonation is workiing. So perhaps I have backed into some weird security issue with the "Run As" feature. I thought this worked on Win 7. Either my memory is wrong, or this is additional security in 8.1. I'll mark you as the answer because you led me to this outcome – John Doh Feb 19 '15 at 15:35
  • Seems like a perverse twist on the "did you run it as an Administrator?" solution – Adam Sep 21 '18 at 20:04