Impersonation and security

 
 
  • Gérald Barré

This post is part of the series 'Vulnerabilities'. Be sure to check out the rest of the blog posts of the series!

Impersonation is a mechanism for performing an action as another user. For example, to execute a specific action you must be an administrator, you use the impersonation to execute the action with an administrator account without logoff and login using the administrator account.

Let's see an example (Do not use it as is):

C#
const string domainName = "meziantou";
const string userName = "administrator";
const string password = "password";

const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;

// Call LogonUser to obtain a handle to an access token.
SafeTokenHandle safeTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
    out safeTokenHandle);

if (false == returnValue)
{
    int ret = Marshal.GetLastWin32Error();
    Console.WriteLine("LogonUser failed with error code : {0}", ret);
    throw new System.ComponentModel.Win32Exception(ret);
}

using (safeTokenHandle)
{
    WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
    WindowsImpersonationContext impersonatedUser = null;
    impersonatedUser = newId.Impersonate();

    // Code now runs under the admin account
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Output Meziantou\Administrator

    impersonatedUser.Dispose(); // End of the impersonated code
}

For the moment this code does what it is asked, but what about security? The risk of such a code is that a method calling this code can execute code as an administrator. You will tell me that this is not possible because we call the Dispose method to finish the impersonation context. Although it happens if the execute code in the impersonation context throws an exception (eg if the hard disk is full)? The method Dispose is no longer called and the calling code continues its execution with administrator rights!!!

C#
public static void Main()
{
   try { Impersonate(); }
   catch { /* Administrator */ }
}

public static void Impersonate()
{
   ...
   impersonatedUser = newId.Impersonate();
   throw new Exception("Fail");
   impersonatedUser.Dispose(); // not called
}

You have to deal with that security issue. There are 2 ways to do this:

  1. Managing the exception with a try/finally

    C#
    impersonatedUser = newId.Impersonate();
    try
    {
        File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Output Meziantou\Administrator
    }
    finally
    {
        impersonatedUser.Dispose();
    }
  2. Using the keyword using

    C#
    using(impersonatedUser = newId.Impersonate())
    {
        File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Output Meziantou\Administrator
    }

The C# compiler will replace this code with a code equivalent to the first method (I let you use a decompiler to check).

Using one of these 2 methods, the method Dispose will be called before the exception goes back to the calling method. So there is no security problem.

Here is the correct complete code:

C#
const string domainName = "meziantou";
const string userName = "administrator";
const string password = "password";

const int LOGON32_PROVIDER_DEFAULT = 0;
// This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;

// Call LogonUser to obtain a handle to an access token.
SafeTokenHandle safeTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
    out safeTokenHandle);

if (false == returnValue)
{
    int ret = Marshal.GetLastWin32Error();
    Console.WriteLine("LogonUser failed with error code : {0}", ret);
    throw new System.ComponentModel.Win32Exception(ret);
}

using (safeTokenHandle)
{
    WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
    using(WindowsImpersonationContext impersonatedUser = newId.Impersonate())
    {
        File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Write Meziantou\Administrator
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub