Windows Impersonation from C#

How do you do Impersonation in .NET?

Here is some good overview of .NET impersonation concepts.

  • Michiel van Otegem: WindowsImpersonationContext made easy
  • WindowsIdentity.Impersonate Method (check out the code samples)

Basically you will be leveraging these classes that are out of the box in the .NET framework:

  • WindowsImpersonationContext
  • WindowsIdentity

The code can often get lengthy though and that is why you see many examples like the one you reference that try to simplify the process.

Impersonating a Windows user

try this :

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);

Usage :

IntPtr userToken = IntPtr.Zero;

bool success = External.LogonUser(
"john.doe",
"domain.com",
"MyPassword",
(int) AdvApi32Utility.LogonType.LOGON32_LOGON_INTERACTIVE, //2
(int) AdvApi32Utility.LogonProvider.LOGON32_PROVIDER_DEFAULT, //0
out userToken);

if (!success)
{
throw new SecurityException("Logon user failed");
}

using (WindowsIdentity.Impersonate(userToken))
{
Process.Start("explorer.exe", @"/root,\\server01-Prod\abc");
}

Impersonate user in Windows Service

I was able to get it to work.

For normal impersonating, I used the following code

public class Impersonation : IDisposable
{
private WindowsImpersonationContext _impersonatedUserContext;

#region FUNCTIONS (P/INVOKE)

// Declare signatures for Win32 LogonUser and CloseHandle APIs
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);

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

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool RevertToSelf();

#endregion

#region ENUMS

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
}

#endregion


/// <summary>
/// Class to allow running a segment of code under a given user login context
/// </summary>
/// <param name="user">domain\user</param>
/// <param name="password">user's domain password</param>
public Impersonation(string domain, string username, string password)
{
var token = ValidateParametersAndGetFirstLoginToken(username, domain, password);

var duplicateToken = IntPtr.Zero;
try
{
if (DuplicateToken(token, 2, ref duplicateToken) == 0)
{


throw new InvalidOperationException("DuplicateToken call to reset permissions for this token failed");
}

var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
_impersonatedUserContext = identityForLoggedOnUser.Impersonate();
if (_impersonatedUserContext == null)
{
throw new InvalidOperationException("WindowsIdentity.Impersonate() failed");
}
}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
if (duplicateToken != IntPtr.Zero)
CloseHandle(duplicateToken);
}
}

private static IntPtr ValidateParametersAndGetFirstLoginToken(string domain, string username, string password)
{


if (!RevertToSelf())
{
ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");

throw new InvalidOperationException("RevertToSelf call to remove any prior impersonations failed");

}

IntPtr token;

var result = LogonUser(domain, username,
password,
LogonSessionType.NewCredentials,
LogonProvider.Default,
out token);
if (!result)
{
var errorCode = Marshal.GetLastWin32Error();
ErrorLogger.LogEvent(string.Format("Could not impersonate the elevated user. LogonUser: {2}\\{1} returned error code: {0}.", errorCode, username, domain), System.Diagnostics.EventLogEntryType.Error, "");
throw new InvalidOperationException("Logon for user " + username + " failed.");
}
return token;
}

public void Dispose()
{
// Stop impersonation and revert to the process identity
if (_impersonatedUserContext != null)
{
_impersonatedUserContext.Undo();
_impersonatedUserContext = null;
}
}
}

To run it I do the following:

            FileInfo fi = new FileInfo(logfile);
using (var imp = new Impersonation(Settings.ImpersonateUser.AccountDomain, Settings.ImpersonateUser.AccountName, Settings.ImpersonateUser.AccountPassword))
{
if (File.Exists(filename))
File.Delete(filename);
fi.MoveTo(filename);
}

For running console commands, I used the following code.

public class CreateProcess
{

#region Constants

const UInt32 INFINITE = 0xFFFFFFFF;
const UInt32 WAIT_FAILED = 0xFFFFFFFF;

#endregion


#region ENUMS

[Flags]
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
LOGON32_LOGON_NEW_CREDENTIALS = 9
}


[Flags]
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35,
LOGON32_PROVIDER_WINNT40,
LOGON32_PROVIDER_WINNT50
}

#endregion


#region Structs

[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}


[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessId;
public Int32 dwThreadId;
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public unsafe byte* lpSecurityDescriptor;
public int bInheritHandle;
}

public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}

public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}

#endregion


#region FUNCTIONS (P/INVOKE)

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool RevertToSelf();

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);


[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean LogonUser
(
String UserName,
String Domain,
String Password,
LogonType dwLogonType,
LogonProvider dwLogonProvider,
out IntPtr phToken
);


[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean CreateProcessAsUser
(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
Boolean bInheritHandles,
Int32 dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);





[DllImport("kernel32.dll", SetLastError = true)]
public static extern UInt32 WaitForSingleObject
(
IntPtr hHandle,
UInt32 dwMilliseconds
);

[DllImport("kernel32", SetLastError = true)]
public static extern Boolean CloseHandle(IntPtr handle);

#endregion

#region Functions

public static int LaunchCommand(string command, string domain, string account, string password)
{
int ProcessId = -1;
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
STARTUPINFO startInfo = new STARTUPINFO();
Boolean bResult = false;

UInt32 uiResultWait = WAIT_FAILED;

var token = ValidateParametersAndGetFirstLoginToken(domain, account, password);

var duplicateToken = IntPtr.Zero;
try
{

startInfo.cb = Marshal.SizeOf(startInfo);
// startInfo.lpDesktop = "winsta0\\default";

bResult = CreateProcessAsUser(
token,
null,
command,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
null,
ref startInfo,
out processInfo
);

if (!bResult) { throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error()); }

// Wait for process to end
uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);

ProcessId = processInfo.dwProcessId;

if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }

}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
if (duplicateToken != IntPtr.Zero)
CloseHandle(duplicateToken);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}

return ProcessId;
}


private static IntPtr ValidateParametersAndGetFirstLoginToken(string domain, string username, string password)
{


if (!RevertToSelf())
{
ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");
throw new Exception("RevertToSelf call to remove any prior impersonations failed");
}

IntPtr token;

var result = LogonUser(username,
domain,
password,
LogonType.LOGON32_LOGON_INTERACTIVE,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out token);
if (!result)
{
var errorCode = Marshal.GetLastWin32Error();
ErrorLogger.LogEvent(string.Format("Could not impersonate the elevated user. LogonUser: {2}\\{1} returned error code: {0}.", errorCode, username, domain), System.Diagnostics.EventLogEntryType.Error, "");
throw new Exception("Logon for user " + username + " failed.");
}
return token;
}

#endregion

}

and executing it doing the following

string commandLine = "Robocopy " + args;

ProcessId = CreateProcess.LaunchCommand(commandLine, ImperDomain, ImperUsername, ImperPassword);

I also had to make some changes to the local policy since I want able to copy permissions in robocopy.

Thanks for all the comments and help.

core 2.0 - best way to impersonate a Windows user?

Found a workable example:

// The following example demonstrates the use of the WindowsIdentity class to impersonate a user. 
// IMPORTANT NOTE:
// This sample asks the user to enter a password on the console screen.
// The password will be visible on the screen, because the console window
// does not support masked input natively.


using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;

public class ImpersonationDemo
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);

public static void Main()
{
// Get the user token for the specified user, domain, and password using the
// unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
Console.Write("Enter the name of the domain on which to log on: ");
string domainName = Console.ReadLine();

Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
string userName = Console.ReadLine();

Console.Write("Enter the password for {0}: ", userName);

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.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);

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

Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
// Check the identity.
Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

// Note: if you want to run as unimpersonated, pass
// 'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'
WindowsIdentity.RunImpersonated(
safeAccessTokenHandle,
// User action
() =>
{
// Check the identity.
Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);
}
);

// Check the identity again.
Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
}
}

https://msdn.microsoft.com/en-us/library/dn906220(v=vs.110).aspx

Is running code under a different user (impersonation) possible with a service account (domain) without a windows service?

OP:

Can a method within a WPF application be executed (using Process.Start) impersonated with a service user account (domain) without a windows service?

You can impersonate a user regardless of what type the calling process is. i.e. WPF, Windows Service, Console App. It does not matter. However on Windows Vista and later the process must be running as an administrator.

Example courtesy of MSDN

string userName, domainName;
// Get the user token for the specified user, domain, and password using the
// unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
Console.Write("Enter the name of the domain on which to log on: ");
domainName = Console.ReadLine();

Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
userName = Console.ReadLine();

Console.Write("Enter the password for {0}: ", userName);

...

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

using (safeTokenHandle)
{
...

using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
// Check the identity.
Console.WriteLine("After impersonation: "
+ WindowsIdentity.GetCurrent().Name);
}
}
}

For more information and the complete example, I recommend viewing the link above as I didn't wish to quote the entire sample.

More

  • WindowsImpersonationContext Class
  • Impersonating and Reverting

Windows Impersonation from C#

It's possible, although it requires you to do a lot of code. See NtCreateToken and CreateToken. You need SeCreateTokenPrivilege, although that won't be a problem since you're running under NT AUTHORITY\SYSTEM. You can then use the created token to impersonate inside a thread.

use Process.Start while Impersonating (Window Application)

You can't use UseShellExecute = true when impersonating. This is related to the way how shell execution works in Windows. Basically everything is passed to the shell which looks up how to handle the verb ("open" in your case) and then starts the application under the user owning the shell, which is not the impersonated user - the impersonated user doesn't actually have a shell if there is no session!

Although you use a different mechanism for impersonating a user the documentation for UseShellExecute still applies in your case:

UseShellExecute must be false if the UserName property is not null or an empty string, or an InvalidOperationException will be thrown when the Process.Start(ProcessStartInfo) method is called.

To solve this issue it might be the easiest to look up the registered application yourself as described in this answer: Finding the default application for opening a particular file type on Windows. With the path to the associated application you can then start the executable as the other user.

How to create a C# service on many network PC's that all impersonate a single AD user account

To solve this, you'll need to do two things:

  1. Set up a Service Account (a special type of unattended account in Active Directory) with the right permissions.
  2. Ensure your C# Services are set up to run as this service account user.

Keep in mind, this is purely for unattended services. If you want a Windows Forms application to impersonate a service account, you're going to have to include the username/password either in the application itself (for instance, in a connection string), or you're going to have to include it in the executable shortcut link.

In either case, it will be visible to the end user (unless of course you encrypt it in the connection string case). But a determined end-user will be able to decrypt it.



Related Topics



Leave a reply



Submit