Thursday, January 04, 2007

Easy impersonation

I've been quite pleasantly surprised recently how easy it is to do impersonation in .net. The trick is a win32 api function that's not covered by the BCL, LogonUser

[System.Runtime.InteropServices.DllImport("advapi32.dll")]
public static extern int LogonUser(
    String lpszUsername, 
    String lpszDomain, 
    String lpszPassword, 
    int dwLogonType, 
    int dwLogonProvider, 
    out  IntPtr phToken);

LogonUser does what it says and logs the given user onto the computer the code is running on. It takes a user name, domain name and a clear text password as well as two constants, a logon type which allows you to specify an interactive user, network user etc and a logon provider (I just used default). It returns a pointer to a token which represents the user and which you can use to create a new WindowsIdentity instance. Once you've got a WindowsIdentity, you can just call the Impersonate method to switch your code execution context to the new identity. Here's a little NUnit test to demonstrate:

[NUnit.Framework.Test]
public void ImpersonationSpike()
{
    string username = "imptest";
    string domain = "mikesMachine"; // this is the machine name
    string password = "imptest";
    IntPtr userToken;

    int hresult = LogonUser(
        username, 
        domain, 
        password, 
        (uint)LogonSessionType.Network, 
        (uint)LogonProvider.Default, 
        out userToken);

    if(hresult == 0)
    {
        int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
        Assert.Fail("Error occured: {0}", error);
    }
    WindowsIdentity identity = new WindowsIdentity(userToken);
    Console.WriteLine(identity.Name);

    Console.WriteLine("I am: '{0}'", WindowsIdentity.GetCurrent().Name);
    System.IO.File.WriteAllText(@"c:\Program Files\hello.txt", "Hello");

    WindowsImpersonationContext context = null;
    try
    {
        context = identity.Impersonate();
        Console.WriteLine("Impersonating: '{0}'", WindowsIdentity.GetCurrent().Name);
    }
    finally
    {
        if(context != null)
        {
            context.Undo();
        }
        if(userToken != IntPtr.Zero)
        {
            CloseHandle(userToken);
        }
    }
    Console.WriteLine("I am: '{0}'", WindowsIdentity.GetCurrent().Name);
}

[System.Runtime.InteropServices.DllImport("advapi32.dll")]
public static extern int LogonUser(
    String lpszUsername, 
    String lpszDomain, 
    String lpszPassword, 
    uint dwLogonType, 
    uint dwLogonProvider, 
    out  IntPtr phToken);

[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);

enum LogonSessionType : uint
{
    Interactive = 2,
    Network,
    Batch,
    Service,
    NetworkCleartext = 8,
    NewCredentials
}

enum LogonProvider : uint
{
    Default = 0, // default for platform (use this!)
    WinNT35,     // sends smoke signals to authority
    WinNT40,     // uses NTLM
    WinNT50      // negotiates Kerb or NTLM
}

It should output the following...

mikesMachine\imptest
I am: 'mikesMachine\mike'
Impersonating: 'mikesMachine\imptest'
I am: 'mikesMachine\mike'

1 passed, 0 failed, 0 skipped, took 1.27 seconds.

A few things to note, you have to have pInvoke permission, which might be problem in some web hosting environments. Also, you have to have a cleartext password in order to log on the user you're impersonating, so your user has to supply it, or you have to store it somewhere which is an obvious security risk.

Of course, if you want to impersonate a particular account in ASP.NET you can just use the <identity impersonate="true" username="theUserName" password="ThePassword" />. On Server 2003, you can even more simply just set your application to run in a custom application pool and set the identity in the application pool settings. All this stuff is explained in this msdn article. But this technique here is great if you just want to grab an identity for a single method call or if you need to do impersonation in something other than an ASP.NET application.

4 comments:

MikeyMikey said...

Thank You, Thank You, Thank You!
Just what i needed (Wanted to access and operate on a filesystem using a service account - did the trick - very little info on the web about this for WinForms)

Thanks again
Mike

Mike Hadlow said...

Thanks for the appreciative comment Mike!

Anonymous said...

Hey Mike, i worked with you in the past at ntl and needed to get impersonation working on a project im working on. Just found your blog and works fine mate. Cheers

Christian

Mike Hadlow said...

Hey Christian, how are you doing? Happy to be of help :)