Я пытался создать новый процесс в контексте определенного пользователя, используя функцию CreateProcessAsUser
API Windows, но, похоже, столкнулся с довольно неприятной проблемой безопасности ...
Прежде чем я объясню подробнее, вот код, который я сейчас использую для запуска нового процесса (консольный процесс - PowerShell, если быть точным, хотя это не должно иметь значения).
private void StartProcess()
{
bool retValue;
// Create startup info for new console process.
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.dwFlags = StartFlags.STARTF_USESHOWWINDOW;
startupInfo.wShowWindow = _consoleVisible ? WindowShowStyle.Show : WindowShowStyle.Hide;
startupInfo.lpTitle = this.ConsoleTitle ?? "Console";
var procAttrs = new SECURITY_ATTRIBUTES();
var threadAttrs = new SECURITY_ATTRIBUTES();
procAttrs.nLength = Marshal.SizeOf(procAttrs);
threadAttrs.nLength = Marshal.SizeOf(threadAttrs);
// Log on user temporarily in order to start console process in its security context.
var hUserToken = IntPtr.Zero;
var hUserTokenDuplicate = IntPtr.Zero;
var pEnvironmentBlock = IntPtr.Zero;
var pNewEnvironmentBlock = IntPtr.Zero;
if (!WinApi.LogonUser("UserName", null, "Password",
LogonType.Interactive, LogonProvider.Default, out hUserToken))
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error logging on user.");
var duplicateTokenAttrs = new SECURITY_ATTRIBUTES();
duplicateTokenAttrs.nLength = Marshal.SizeOf(duplicateTokenAttrs);
if (!WinApi.DuplicateTokenEx(hUserToken, 0, ref duplicateTokenAttrs,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary,
out hUserTokenDuplicate))
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error duplicating user token.");
try
{
// Get block of environment vars for logged on user.
if (!WinApi.CreateEnvironmentBlock(out pEnvironmentBlock, hUserToken, false))
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Error getting block of environment variables for user.");
// Read block as array of strings, one per variable.
var envVars = ReadEnvironmentVariables(pEnvironmentBlock);
// Append custom environment variables to list.
foreach (var var in this.EnvironmentVariables)
envVars.Add(var.Key + "=" + var.Value);
// Recreate environment block from array of variables.
var newEnvironmentBlock = string.Join("\0", envVars.ToArray()) + "\0";
pNewEnvironmentBlock = Marshal.StringToHGlobalUni(newEnvironmentBlock);
// Start new console process.
retValue = WinApi.CreateProcessAsUser(hUserTokenDuplicate, null, this.CommandLine,
ref procAttrs, ref threadAttrs, false, CreationFlags.CREATE_NEW_CONSOLE |
CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
if (!retValue) throw new Win32Exception(Marshal.GetLastWin32Error(),
"Unable to create new console process.");
}
catch
{
// Catch any exception thrown here so as to prevent any malicious program operating
// within the security context of the logged in user.
// Clean up.
if (hUserToken != IntPtr.Zero)
{
WinApi.CloseHandle(hUserToken);
hUserToken = IntPtr.Zero;
}
if (hUserTokenDuplicate != IntPtr.Zero)
{
WinApi.CloseHandle(hUserTokenDuplicate);
hUserTokenDuplicate = IntPtr.Zero;
}
if (pEnvironmentBlock != IntPtr.Zero)
{
WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
pEnvironmentBlock = IntPtr.Zero;
}
if (pNewEnvironmentBlock != IntPtr.Zero)
{
Marshal.FreeHGlobal(pNewEnvironmentBlock);
pNewEnvironmentBlock = IntPtr.Zero;
}
throw;
}
finally
{
// Clean up.
if (hUserToken != IntPtr.Zero)
WinApi.CloseHandle(hUserToken);
if (hUserTokenDuplicate != IntPtr.Zero)
WinApi.CloseHandle(hUserTokenDuplicate);
if (pEnvironmentBlock != IntPtr.Zero)
WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
if (pNewEnvironmentBlock != IntPtr.Zero)
Marshal.FreeHGlobal(pNewEnvironmentBlock);
}
_process = Process.GetProcessById(_processInfo.dwProcessId);
}
Ради этой проблемы проигнорируйте код, относящийся к переменным среды (я протестировал этот раздел независимо, и, похоже, он работает).
Теперь я получаю следующую ошибку (в строке после вызова CreateProcessAsUSer
):
«Требуемая привилегия не удерживается клиентом» (код ошибки 1314)
(Сообщение об ошибке было обнаружено путем удаления параметра сообщения из конструктора Win32Exception. Конечно, мой код обработки ошибок здесь может быть не самым лучшим, но это несколько неуместно. Вы можете прокомментировать его, если хотите , однако.) Я действительно очень запутался относительно причины этой неопределенной ошибки в этой ситуации. Документация MSDN и различные ветки форума давали мне очень много советов, и особенно учитывая, что причины таких ошибок, как представляется, сильно различаются, я понятия не имею, какой раздел кода мне нужно изменить. Возможно, это просто один параметр, который мне нужно изменить, но я мог делать неправильные / недостаточные вызовы WinAPI для всего, что я знаю. Что меня сильно смущает, так это то, что предыдущая версия кода, использующая простую функцию CreateProcess
(эквивалентную, кроме параметра токена пользователя), работала отлично. Как я понимаю, необходимо только вызвать пользовательскую функцию входа в систему, чтобы получить соответствующий маркер токена, а затем продублировать его, чтобы он мог быть передан CreateProcessAsUser
.
Буду очень рад любым предложениям по модификации кода, а также объяснениям.
Примечания
Я в основном ссылался на документы MSDN (а также PInvoke.net для объявлений функции C # / strut / enum). Следующие страницы, в частности, содержат много информации в разделах «Замечания», некоторые из которых могут быть важными и ускользать от меня:
Редактировать
Я только что опробовал предложение Митча, но, к сожалению, старая ошибка была заменена новой: «Система не может найти указанный файл». (код ошибки 2)
Предыдущий вызов CreateProcessAsUser
был заменен следующим:
retValue = WinApi.CreateProcessWithTokenW(hUserToken, LogonFlags.WithProfile, null,
this.CommandLine, CreationFlags.CREATE_NEW_CONSOLE |
CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
Обратите внимание, что в этом коде больше не используется дублирующий токен, а скорее оригинал, как это видно из документов MSDN.
А вот еще одна попытка использования CreateProcessWithLogonW
. На этот раз ошибка «Ошибка входа в систему: неизвестное имя пользователя или неверный пароль» (код ошибки 1326)
retValue = WinApi.CreateProcessWithLogonW("Alex", null, "password",
LogonFlags.WithProfile, null, this.CommandLine,
CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED |
CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock,
null, ref startupInfo, out _processInfo);
Я также попытался указать имя пользователя в формате UPN («Alex @ Alex-PC») и передать домен независимо в качестве второго аргумента, но все безрезультатно (идентичная ошибка).