C# 使用来自 Windows 服务的凭据启动进程
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/677874/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Starting a process with credentials from a Windows Service
提问by Zack Elan
I have a Windows service that runs as mydomain\userA. I want to be able to run arbitrary .exes from the service. Normally, I use Process.Start() and it works fine, but in some cases I want to run the executable as a different user (mydomain\userB).
我有一个以 mydomain\userA 身份运行的 Windows 服务。我希望能够从服务中运行任意 .exe。通常,我使用 Process.Start() 并且它工作正常,但在某些情况下,我想以不同的用户 (mydomain\userB) 运行可执行文件。
If I change the ProcessStartInfo I use to start the process to include credentials, I start getting errors - either an error dialog box that says "The application failed to initialize properly (0xc0000142). Click on OK to terminate the application.", or an "Access is denied" Win32Exception. If I run the process-starting code from the command-line instead of running it in the service, the process starts using the correct credentials (I've verified this by setting the ProcessStartInfo to run whoami.exe and capturing the command-line output).
如果我更改用于启动进程的 ProcessStartInfo 以包含凭据,我会开始收到错误消息 - 一个错误对话框显示“应用程序无法正确初始化 (0xc0000142)。单击确定终止应用程序。”,或“访问被拒绝” Win32Exception。如果我从命令行运行进程启动代码而不是在服务中运行它,进程将开始使用正确的凭据(我已经通过将 ProcessStartInfo 设置为运行 whoami.exe 并捕获命令行输出来验证这一点)。
I've also tried impersonation using WindowsIdentity.Impersonate(), but this hasn't worked - as I understand it, impersonation only affects the current thread, and starting a new process inherits the process' security descriptor, not the current thread.
我也尝试过使用 WindowsIdentity.Impersonate() 进行模拟,但这并没有奏效——据我所知,模拟只影响当前线程,并且启动一个新进程会继承进程的安全描述符,而不是当前线程。
I'm running this in an isolated test domain, so both userA and userB are domain admins, and both have the Log On as a Service right domain-wide.
我在一个隔离的测试域中运行它,所以 userA 和 userB 都是域管理员,并且都具有域范围内的“登录即服务”权限。
采纳答案by Stephen Martin
When you launch a new process using ProcessStartInfo the process is started in the same window station and desktop as the launching process. If you are using different credentials then the user will, in general, not have sufficient rights to run in that desktop. The failure to initialize errors are caused when user32.dll attempts to initialize in the new process and can't.
当您使用 ProcessStartInfo 启动新进程时,该进程将在与启动进程相同的窗口站和桌面中启动。如果您使用不同的凭据,则用户通常没有足够的权限在该桌面上运行。初始化失败错误是因为user32.dll尝试在新进程中初始化,但不能。
To get around this you must first retrieve the security descriptors associated with the window station and desktop and add the appropriate permissions to the DACL for your user, then launch your process under the new credentials.
要解决此问题,您必须首先检索与窗口站和桌面相关联的安全描述符,并为您的用户向 DACL 添加适当的权限,然后在新凭据下启动您的进程。
EDIT: A detailed description on how to do this and sample code was a little long for here so I put together an articlewith code.
编辑:有关如何执行此操作和示例代码的详细说明在这里有点长,因此我将一篇带有代码的文章放在一起。
//The following security adjustments are necessary to give the new
//process sufficient permission to run in the service's window station
//and desktop. This uses classes from the AsproLock library also from
//Asprosys.
IntPtr hWinSta = GetProcessWindowStation();
WindowStationSecurity ws = new WindowStationSecurity(hWinSta,
System.Security.AccessControl.AccessControlSections.Access);
ws.AddAccessRule(new WindowStationAccessRule("LaunchProcessUser",
WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AcceptChanges();
IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
DesktopSecurity ds = new DesktopSecurity(hDesk,
System.Security.AccessControl.AccessControlSections.Access);
ds.AddAccessRule(new DesktopAccessRule("LaunchProcessUser",
DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AcceptChanges();
EventLog.WriteEntry("Launching application.", EventLogEntryType.Information);
using (Process process = Process.Start(psi))
{
}
回答by casperOne
How are you setting the domain, user, and password? Are you setting the domain properly as well as the password (it must use a SecureString).
你如何设置域、用户和密码?您是否正确设置了域以及密码(它必须使用 SecureString)。
Also, are you setting the WorkingDirectory property? When using a UserName and Password, the documentation states that you must set the WorkingDirectory property.
另外,您是否设置了 WorkingDirectory 属性?使用用户名和密码时,文档指出您必须设置 WorkingDirectory 属性。
回答by lsalamon
This is symptomatic of :
- insufficient rights;
- failure load of a library;
这是以下症状的症状:
- 权利不足;
- 库的失败负载;
Use Filemon to detect some access denied or
WinDbg to run the application in a debugger and view any issue.
使用 Filemon 检测某些访问被拒绝或使用
WinDbg 在调试器中运行应用程序并查看任何问题。
回答by Moose
It may be that any process kicked off by a service must also have the "Log on as a Service" privelege.
可能由服务启动的任何进程也必须具有“作为服务登录”特权。
If the user id that you are using to start the second process does not have administrative rights to the box, this could be the case.
如果您用来启动第二个进程的用户 ID 没有该框的管理权限,则可能是这种情况。
An easy test would be to change the local security policy to give the userid "Log on as a service" and try it again.
一个简单的测试是更改本地安全策略以提供用户标识“作为服务登录”,然后再试一次。
Edit: After the additional info..
编辑:在附加信息之后..
Grazing over Google on this one, it appears that 0xc0000142 relates to not being able to initialize a needed DLL. Is there something that the service has open that the spawned process needs? In any case, it looks like it has to do with the process that's kicked off, and not how you are doing it.
在这个问题上掠过谷歌,似乎 0xc0000142 与无法初始化所需的 DLL 相关。服务是否已打开生成的进程需要的某些内容?在任何情况下,它看起来都与开始的过程有关,而不是你如何做。
回答by Adam Says - Reinstate Monica
I had this problem today, and I spent quite some time trying to figure it out. What I ended up doing was to create the service as interactive (using the Allow service to interact with desktop checkbox in services.msc). As soon as I did that the 0xc0000142 errors went away.
我今天遇到了这个问题,我花了很多时间试图弄清楚。我最终做的是创建交互式服务(使用 services.msc 中的允许服务与桌面交互复选框)。一旦我这样做了 0xc0000142 错误就消失了。
回答by Martin Prikryl
Based on the answer by @StephenMartin.
基于@StephenMartin的回答。
A new process launched using the Process
class runs in the same window station and desktop as the launching process. If you are running the new process using different credentials, then the new process won't have permissions to access the window station and desktop. What results in errors like 0xC0000142.
使用Process
该类启动的新进程在与启动进程相同的窗口站和桌面中运行。如果您使用不同的凭据运行新进程,则新进程将无权访问窗口站和桌面。什么会导致像 0xC0000142 这样的错误。
The following is a "compact" standalone code to grant a user an access to the current window station and desktop. It does not require the AsproLock library.
以下是授予用户访问当前窗口站和桌面的“紧凑”独立代码。它不需要 AsproLock 库。
Call the GrantAccessToWindowStationAndDesktop
method with the username you use to run the Process
(Process.StartInfo.UserName
), before calling Process.Start
.
调用GrantAccessToWindowStationAndDesktop
与您使用的运行用户名法Process
(Process.StartInfo.UserName
)调用之前Process.Start
。
public static void GrantAccessToWindowStationAndDesktop(string username)
{
IntPtr handle;
const int WindowStationAllAccess = 0x000f037f;
handle = GetProcessWindowStation();
GrantAccess(username, handle, WindowStationAllAccess);
const int DesktopRightsAllAccess = 0x000f01ff;
handle = GetThreadDesktop(GetCurrentThreadId());
GrantAccess(username, handle, DesktopRightsAllAccess);
}
private static void GrantAccess(string username, IntPtr handle, int accessMask)
{
SafeHandle safeHandle = new NoopSafeHandle(handle);
GenericSecurity security =
new GenericSecurity(
false, ResourceType.WindowObject, safeHandle, AccessControlSections.Access);
security.AddAccessRule(
new GenericAccessRule(
new NTAccount(username), accessMask, AccessControlType.Allow));
security.Persist(safeHandle, AccessControlSections.Access);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
// All the code to manipulate a security object is available in .NET framework,
// but its API tries to be type-safe and handle-safe, enforcing a special implementation
// (to an otherwise generic WinAPI) for each handle type. This is to make sure
// only a correct set of permissions can be set for corresponding object types and
// mainly that handles do not leak.
// Hence the AccessRule and the NativeObjectSecurity classes are abstract.
// This is the simplest possible implementation that yet allows us to make use
// of the existing .NET implementation, sparing necessity to
// P/Invoke the underlying WinAPI.
private class GenericAccessRule : AccessRule
{
public GenericAccessRule(
IdentityReference identity, int accessMask, AccessControlType type) :
base(identity, accessMask, false, InheritanceFlags.None,
PropagationFlags.None, type)
{
}
}
private class GenericSecurity : NativeObjectSecurity
{
public GenericSecurity(
bool isContainer, ResourceType resType, SafeHandle objectHandle,
AccessControlSections sectionsRequested)
: base(isContainer, resType, objectHandle, sectionsRequested)
{
}
new public void Persist(SafeHandle handle, AccessControlSections includeSections)
{
base.Persist(handle, includeSections);
}
new public void AddAccessRule(AccessRule rule)
{
base.AddAccessRule(rule);
}
#region NativeObjectSecurity Abstract Method Overrides
public override Type AccessRightType
{
get { throw new NotImplementedException(); }
}
public override AccessRule AccessRuleFactory(
System.Security.Principal.IdentityReference identityReference,
int accessMask, bool isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AccessControlType type)
{
throw new NotImplementedException();
}
public override Type AccessRuleType
{
get { return typeof(AccessRule); }
}
public override AuditRule AuditRuleFactory(
System.Security.Principal.IdentityReference identityReference, int accessMask,
bool isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AuditFlags flags)
{
throw new NotImplementedException();
}
public override Type AuditRuleType
{
get { return typeof(AuditRule); }
}
#endregion
}
// Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed
private class NoopSafeHandle : SafeHandle
{
public NoopSafeHandle(IntPtr handle) :
base(handle, false)
{
}
public override bool IsInvalid
{
get { return false; }
}
protected override bool ReleaseHandle()
{
return true;
}
}
回答by Martin Prikryl
Based on the answer by @Stephen Martin and Martin Prikryl.
基于@Stephen Martin 和 Martin Prikryl 的回答。
This code helps you to run a process with different user credentials from a service.
I have now optimized the source code.
The removal and setting of rights is now also possible.
此代码可帮助您使用来自服务的不同用户凭据运行进程。
我现在已经优化了源代码。
现在也可以删除和设置权限。
namespace QlikConnectorPSExecute
{
#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
#endregion
//inspired by: http://stackoverflow.com/questions/677874/starting-a-process-with-credentials-from-a-windows-service
public class WindowsGrandAccess : IDisposable
{
#region DLL-Import
// All the code to manipulate a security object is available in .NET framework,
// but its API tries to be type-safe and handle-safe, enforcing a special implementation
// (to an otherwise generic WinAPI) for each handle type. This is to make sure
// only a correct set of permissions can be set for corresponding object types and
// mainly that handles do not leak.
// Hence the AccessRule and the NativeObjectSecurity classes are abstract.
// This is the simplest possible implementation that yet allows us to make use
// of the existing .NET implementation, sparing necessity to
// P/Invoke the underlying WinAPI.
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
#endregion
#region Variables && Properties
public static int WindowStationAllAccess { get; private set; } = 0x000f037f;
public static int DesktopRightsAllAccess { get; private set; } = 0x000f01ff;
private GenericSecurity WindowStationSecurity {get; set;}
private GenericSecurity DesktopSecurity { get; set; }
private int? OldWindowStationMask { get; set; }
private int? OldDesktopMask { get; set; }
private NTAccount AccountInfo { get; set; }
private SafeHandle WsSafeHandle { get; set; }
private SafeHandle DSafeHandle { get; set; }
#endregion
#region Constructor & Dispose
public WindowsGrandAccess(NTAccount accountInfo, int windowStationMask, int desktopMask)
{
if (accountInfo != null)
Init(accountInfo, windowStationMask, desktopMask);
}
public void Dispose()
{
try
{
if (AccountInfo == null)
return;
RestAccessMask(OldWindowStationMask, WindowStationAllAccess, WindowStationSecurity, WsSafeHandle);
RestAccessMask(OldDesktopMask, DesktopRightsAllAccess, DesktopSecurity, DSafeHandle);
}
catch (Exception ex)
{
throw new Exception($"The object \"{nameof(WindowsGrandAccess)}\" could not be dispose.", ex);
}
}
#endregion
#region Methods
private void Init(NTAccount accountInfo, int windowStationMask, int desktopMask)
{
AccountInfo = accountInfo;
WsSafeHandle = new NoopSafeHandle(GetProcessWindowStation());
WindowStationSecurity = new GenericSecurity(false, ResourceType.WindowObject, WsSafeHandle, AccessControlSections.Access);
DSafeHandle = new NoopSafeHandle(GetThreadDesktop(GetCurrentThreadId()));
DesktopSecurity = new GenericSecurity(false, ResourceType.WindowObject, DSafeHandle, AccessControlSections.Access);
OldWindowStationMask = ReadAccessMask(WindowStationSecurity, WsSafeHandle, windowStationMask);
OldDesktopMask = ReadAccessMask(DesktopSecurity, DSafeHandle, desktopMask);
}
private AuthorizationRuleCollection GetAccessRules(GenericSecurity security)
{
return security.GetAccessRules(true, false, typeof(NTAccount));
}
private int? ReadAccessMask(GenericSecurity security, SafeHandle safeHandle, int accessMask)
{
var ruels = GetAccessRules(security);
var username = AccountInfo.Value;
if (!username.Contains("\"))
username = $"{Environment.MachineName}\{username}";
var userResult = ruels.Cast<GrantAccessRule>().SingleOrDefault(r => r.IdentityReference.Value.ToLower() == username.ToLower() && accessMask == r.PublicAccessMask);
if (userResult == null)
{
AddGrandAccess(security, accessMask, safeHandle);
userResult = ruels.Cast<GrantAccessRule>().SingleOrDefault(r => r.IdentityReference.Value.ToLower() == username.ToLower());
if (userResult != null)
return userResult.PublicAccessMask;
}
else
return userResult.PublicAccessMask;
return null;
}
private void AddGrandAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle)
{
var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow);
security.AddAccessRule(rule);
security.Persist(safeHandle, AccessControlSections.Access);
}
private void RemoveGrantAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle)
{
var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow);
security.RemoveAccessRule(rule);
security.Persist(safeHandle, AccessControlSections.Access);
}
private void SetGrandAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle)
{
var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow);
security.SetAccessRule(rule);
security.Persist(safeHandle, AccessControlSections.Access);
}
private void RestAccessMask(int? oldAccessMask, int fullAccessMask, GenericSecurity security, SafeHandle safeHandle)
{
if (oldAccessMask == null)
RemoveGrantAccess(security, fullAccessMask, safeHandle);
else if (oldAccessMask != fullAccessMask)
{
SetGrandAccess(security, oldAccessMask.Value, safeHandle);
}
}
#endregion
#region private classes
private class GenericSecurity : NativeObjectSecurity
{
public GenericSecurity(
bool isContainer, ResourceType resType, SafeHandle objectHandle,
AccessControlSections sectionsRequested)
: base(isContainer, resType, objectHandle, sectionsRequested) { }
new public void Persist(SafeHandle handle, AccessControlSections includeSections)
{
base.Persist(handle, includeSections);
}
new public void AddAccessRule(AccessRule rule)
{
base.AddAccessRule(rule);
}
new public bool RemoveAccessRule(AccessRule rule)
{
return base.RemoveAccessRule(rule);
}
new public void SetAccessRule(AccessRule rule)
{
base.SetAccessRule(rule);
}
new public AuthorizationRuleCollection GetAccessRules(bool includeExplicit, bool includeInherited, Type targetType)
{
return base.GetAccessRules(includeExplicit, includeInherited, targetType);
}
public override Type AccessRightType
{
get { throw new NotImplementedException(); }
}
public override AccessRule AccessRuleFactory(
System.Security.Principal.IdentityReference identityReference,
int accessMask, bool isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AccessControlType type)
{
return new GrantAccessRule(identityReference, accessMask, isInherited, inheritanceFlags, propagationFlags, type);
}
public override Type AccessRuleType
{
get { return typeof(AccessRule); }
}
public override AuditRule AuditRuleFactory(
System.Security.Principal.IdentityReference identityReference, int accessMask,
bool isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AuditFlags flags)
{
throw new NotImplementedException();
}
public override Type AuditRuleType
{
get { return typeof(AuditRule); }
}
}
private class GrantAccessRule : AccessRule
{
public GrantAccessRule(IdentityReference identity, int accessMask, bool isInherited,
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags,
AccessControlType type) :
base(identity, accessMask, isInherited,
inheritanceFlags, propagationFlags, type) { }
public GrantAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
base(identity, accessMask, false, InheritanceFlags.None,
PropagationFlags.None, type) { }
public int PublicAccessMask
{
get { return base.AccessMask; }
}
}
// Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed
private class NoopSafeHandle : SafeHandle
{
public NoopSafeHandle(IntPtr handle) :
base(handle, false) {}
public override bool IsInvalid
{
get { return false; }
}
protected override bool ReleaseHandle()
{
return true;
}
}
#endregion
}
}
Using Sample
使用示例
using (var windowsAccess = new WindowsGrandAccess(accountInfo, WindowsGrandAccess.WindowStationAllAccess, WindowsGrandAccess.DesktopRightsAllAccess))
{
...
}
Thank you.
谢谢你。
回答by MatthewSegal
I have reimplemented Martin Prikryl's answer in Python, which I hope someone finds useful.
我已经用 Python 重新实现了 Martin Prikryl 的答案,我希望有人觉得它有用。
I ran into this problem running a subprocess in a Python script. I was using the pythonnet
package to run System.Diagnostics.Process
as different user. My issue was that the subprocess was not running and I received no stdout or stderr.
我在 Python 脚本中运行子进程时遇到了这个问题。我正在使用该pythonnet
包以System.Diagnostics.Process
不同的用户身份运行。我的问题是子进程没有运行,我没有收到 stdout 或 stderr。
# Import .NET objects using pythonnet
from System.Diagnostics import Process
# Use .NET API to run a subprocess using the given executable
# as the target user, in the provided working directory.
process = Process()
process.StartInfo.UseShellExecute = False
process.StartInfo.CreateNoWindow = True
process.StartInfo.LoadUserProfile = True
process.StartInfo.RedirectStandardOutput = True
process.StartInfo.RedirectStandardError = True
process.StartInfo.WorkingDirectory = working_dir
process.StartInfo.Domain = "mydomain"
process.StartInfo.UserName = username.lower().replace("mydomain\", "")
process.StartInfo.PasswordInClearText = password
process.StartInfo.FileName = executable
process.StartInfo.Arguments = " ".join(args)
# Run the subprocess.
process.Start()
# Read subprocess console output
stdout = process.StandardOutput.ReadToEnd()
stderr = process.StandardError.ReadToEnd()
log.info(f"\n{executable} subprocess stdout:\n\n{stdout}")
log.info(f"{executable} subprocess stderr:\n\n{stderr}")
log.info(f"Done running {executable} as {username}.")
I used Martin Prikryl's answer, but I reimplemented it in Python using the pyWin32 library, which solved my issue.:
我使用了 Martin Prikryl 的答案,但我使用 pyWin32 库在 Python 中重新实现了它,这解决了我的问题。:
import win32api, win32process, win32service, win32security
WINDOW_STATION_ALL_ACCESS = 983935
DESKTOP_RIGHTS_ALL_ACCESS = 983551
SE_WINDOW_OBJECT = 7
DACL_SECURITY_INFORMATION = 4
def set_access(user, handle, access):
info = win32security.GetSecurityInfo(
handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION
)
dacl = info.GetSecurityDescriptorDacl()
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, access, user)
win32security.SetSecurityInfo(
handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION, None, None, dacl, None
)
username = "mattsegal"
user, domain, user_type = win32security.LookupAccountName("", username)
thread_id = win32api.GetCurrentThreadId()
station_handle = win32process.GetProcessWindowStation()
desktop_handle = win32service.GetThreadDesktop(thread_id)
set_access(user, station_handle, WINDOW_STATION_ALL_ACCESS)
set_access(user, desktop_handle, DESKTOP_RIGHTS_ALL_ACCESS)