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 actions as another user. For example, if a specific action requires administrator privileges, you can use impersonation to execute it under an administrator account without logging off and back in.
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
}
This code works as intended, but what about security? The risk is that code calling this method could inadvertently run as an administrator. You might think this is not possible because Dispose is called to end the impersonation context. But what happens if the code inside the impersonation context throws an exception (for example, if the hard disk is full)? The Dispose method is never called, and the calling code continues executing 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:
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();
}
Using the keyword using
C#
using(impersonatedUser = newId.Impersonate())
{
File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Output Meziantou\Administrator
}
The C# compiler transforms this into code equivalent to the first method (you can verify this with a decompiler).
With either approach, Dispose is guaranteed to be called before the exception propagates to the calling method, eliminating the security risk.
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!