Impersonation and security

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

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):

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();

    // Les actions sont effectuées avec le compte administrator
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Ecrit Meziantou\Administrator

    impersonatedUser.Dispose(); // Fin du code ayant besoin des droits administrateur
}

For the moment this code does what it is asked, but what about security? In fact 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!!!

public static void Main()
{
   try { Impersonate(); }
   catch { /* Administrateur */ }
}

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
impersonatedUser = newId.Impersonate();
try
{
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Affiche Meziantou\Administrator
}
finally
{
    impersonatedUser.Dispose();
}
  1. Using the keyword using
using(impersonatedUser = newId.Impersonate())
{
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Affiche 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:

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
    }
}

Leave a reply