Impersonation and security
This post is part of the series 'Vulnerabilities'. Be sure to check out the rest of the blog posts of the series!
- Impersonation and security (this post)
- SQL injections
- How to prevent CSRF attacks
- ASP MVC and XSRF
- Cross-site scripting (XSS)
- ASP MVC: Mass Assignment
- Regex - Deny of Service (ReDoS)
- Deserialization can be dangerous
- Prevent Zip bombs in a .NET application
- Prevent Zip Slip in .NET
- How to protect against XML vulnerabilities in .NET
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();
// 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!!!
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 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
}
}
Do you have a question or a suggestion about this post? Contact me!