c# - Hacerse pasar por el usuario en el servicio de Windows
c#-4.0 windows-services (1)
Pude hacerlo funcionar.
Para suplantación normal, utilicé el siguiente código
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;
}
}
}
Para ejecutarlo hago lo siguiente:
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);
}
Para ejecutar los comandos de la consola, utilicé el siguiente código.
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
}
y ejecutandolo haciendo lo siguiente
string commandLine = "Robocopy " + args;
ProcessId = CreateProcess.LaunchCommand(commandLine, ImperDomain, ImperUsername, ImperPassword);
También tuve que hacer algunos cambios en la política local ya que quiero poder copiar los permisos en robocopy.
Gracias por todos los comentarios y ayuda.
Estoy tratando de suplantar a un usuario del dominio en un servicio de Windows con el servicio registrado como la Cuenta del sistema local.
Hasta ahora, solo puedo hacer que esto funcione al registrar el servicio y configurar el proceso con las credenciales de usuario, como las siguientes.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = CommandDetails.Command;
startInfo.WorkingDirectory = Settings.RoboCopyWorkingDirectory;
startInfo.Arguments = commandLine;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
// Credentials
startInfo.Domain = ImperDomain;
startInfo.UserName = ImperUsername;
startInfo.Password = ImperPasswordSecure;
process = Process.Start(startInfo);
Mi objetivo es no tener el registro de servicio en un usuario del dominio, sino como un sistema local, ya que las contraseñas de las cuentas de dominio se restablecen.
Cuando uso el sistema local, obtengo el acceso denegado.
¿Alguna idea de cómo lograr esto?
StackTace
Access is denied
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at Ace.WindowsService.ProcessCmd.ProcessCommand.StartProcess(ProcessStartInfo startInfo) in
He intentado envolver el código en el código de suplantación indicado a continuación sin éxito.
Personificar el código
public class Impersonation2 : IDisposable
{
private WindowsImpersonationContext _impersonatedUserContext;
// 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();
// ReSharper disable UnusedMember.Local
enum LogonSessionType : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
// ReSharper disable InconsistentNaming
enum LogonProvider : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
// ReSharper restore InconsistentNaming
// ReSharper restore UnusedMember.Local
/// <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 Impersonation2(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 Exception("DuplicateToken call to reset permissions for this token failed");
}
var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
_impersonatedUserContext = identityForLoggedOnUser.Impersonate();
if (_impersonatedUserContext == null)
{
throw new Exception("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())
{
throw new Exception("RevertToSelf call to remove any prior impersonations failed");
ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");
}
IntPtr token;
var result = LogonUser(domain, username,
password,
LogonSessionType.Interactive,
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 Exception("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;
}
}
Actualizar
Esto funciona bien si solo estoy ejecutando si solo lo estoy ejecutando. Pero cuando se ejecuta como un servicio, no funcionará
Actualización 2
No obtengo el acceso denegado desde Process.Start cuando cambio el inicio de sesión de suplantación a LogonSessionType.NewCredentials y elimino los crednetials del proceso. Pero ahora veo un error al ejecutar el comando robocopy. Cuando tengo las credenciales en el proceso, no produce un archivo de registro desde el comando robocopy
Error
2016/07/16 09:19:12 ERROR 5 (0x00000005)
Accessing Source Directory //[server]/[path]/
Access is denied.
Cambio
var result = LogonUser(domain, username,
password,
LogonSessionType.NewCredentials,
LogonProvider.Default,
out token);
Actualización 3
Las funciones de copiar y mover están funcionando. Pero crear subproceso no lo es. He estado jugando con CreateProcessAsUser como lo ha sugerido Hary Johnston.