【问题标题】:Starting processes under specific credentials from a Windows service从 Windows 服务以特定凭据启动进程
【发布时间】:2014-03-09 14:38:06
【问题描述】:

我在这个问题上花了几天时间,尽管网上有很多不同的例子,但这是一个棘手的问题,我无法让它们在我的场景中工作。

我有一个在本地系统帐户下运行的 Windows 服务。它有一个监听 API 请求的 WCF 端点。当通过 API 告知时,该服务应该在系统会话 (0) 中启动一个新进程,并使用“Worker”帐户凭据。该进程是一个工作人员,它检查队列中的工作并执行它。如果它没有找到工作,它将休眠一段时间并再次检查。如果它确实找到了工作,它会在同一个会话中使用相同的凭据启动一个新进程并完成工作。完成后关闭。

“Worker”是域帐户,是机器上本地管理员组的成员,对可执行文件具有执行权限。该机器与帐户在同一个域中。

问题在于,当服务尝试启动进程时,它会从CreateProcessAsUser 方法中获得ERROR_ACCESS_DENIED (5) 错误代码。

我尝试在具有相同凭据的 Windows 7 机器上运行相同的代码,它工作正常,但在 Windows Server 2008 上运行时会收到该错误代码。

代码太大,这里无法展示,所以我把它放在了别处……

ProcessHelperhttp://pastie.org/private/y7idu3nw4xv1fxzeizbn9g

服务调用StartAsUserFromService方法启动进程,建立会话后内部调用CreateProcessAsUser。该进程调用StartAsUserFromApplication方法来启动它的后继进程,它内部调用CreateProcessWithLogonW

ImpersonationContexthttp://pastie.org/private/xppc7wnoidajmpq8h8sg

服务需要获取用户令牌才能以他们的身份启动进程。该过程不需要它来启动其继任者。据我所知,模拟在 Server 2008 上是成功的,但它没有一些权限,我不知道是哪个。

编辑:

我在 Windows 7 机器上尝试了本地管理员帐户和域帐户,它们工作正常。但是它们都不能在 Server 2008 机器上工作。一定是某处缺少权限,但我不知道在哪里;错误消息没有帮助。

我也尝试在可执行文件的兼容性选项卡中勾选“以管理员身份运行”框,但没有任何区别。

编辑:

我使用进程监视器来查看服务中发生了什么,这就是它得到错误的地方......

Date & Time:    12/02/2014 11:44:03
Event Class:    File System
Operation:  CreateFile
Result: ACCESS DENIED
Path:   D:\..\executable.exe
TID:    6244
Duration:   0.0000450
Desired Access: Read Data/List Directory, Execute/Traverse, Read Attributes, Synchronize
Disposition:    Open
Options:    Synchronous IO Non-Alert, Non-Directory File
Attributes: n/a
ShareMode:  Read, Delete
AllocationSize: n/a
Impersonating:  Domain\Worker

Date & Time:    12/02/2014 11:44:03
Event Class:    File System
Operation:  CreateFile
Result: ACCESS DENIED
Path:   D:\..\executable.exe
TID:    6244
Duration:   0.0000480
Desired Access: Execute/Traverse, Synchronize
Disposition:    Open
Options:    Synchronous IO Non-Alert, Non-Directory File
Attributes: n/a
ShareMode:  Read, Delete
AllocationSize: n/a
Impersonating:  Domain\Worker

【问题讨论】:

  • 域用户是否有作为服务登录的权限? technet.microsoft.com/en-us/library/cc739424(v=ws.10).aspx
  • @David 是的。即使它不应该需要它们,因为我没有使用LogonType.Service 将流程作为服务启动。我以LogonType.Interactive 开头。
  • 哦,我真傻。这是我问的一个愚蠢的问题。对不起。
  • @David 没关系,我愿意测试任何想法。我还尝试在服务管理器中勾选“允许服务与桌面交互”框,但没有帮助。进程/用户帐户是否有类似的权限?
  • 您不希望您的服务与桌面交互。而且您可能不希望它在 LocalSystem 下运行。安全日志是否报告您登录失败的原因?

标签: c# winapi process


【解决方案1】:

我设法让进程从这段代码开始:

ProcessHelperhttp://pastie.org/private/dlkytj8rbigs8ixwtg

TokenImpersonationContexthttp://pastie.org/private/nu3pvpghoea6pwwlvjuq

服务调用StartAsUserFromService方法,进程调用StartAsUserFromApplication方法启动其后继。

我在LogonUser 调用中使用LogonType.Batch,因为该进程需要与另一个WCF 服务通信并且需要进行身份验证。 LogonType.NetworkLogonType.NetworkClearText 可以使用,但在 Net.Tcp 端口共享服务中与 Worker 用户帐户导致权限问题。

这个答案很有帮助:Using Process.Start() to start a process as a different user from within a Windows Service

【讨论】:

  • 对链接感到抱歉,它们已经消失了。
【解决方案2】:

一些提示:
How to Impersonate
Impersonation code in C#
Impersonation Libraries (Class & Com Class)
WindowsIdentity.Impersonate Method

尝试使用此示例(在某处找到):

使用系统; 使用 System.Runtime.InteropServices; 使用 System.Security.Principal; 使用 System.Security.Permissions; [程序集:SecurityPermissionAttribute(SecurityAction.RequestMinimum,UnmanagedCode=true)] [程序集:PermissionSetAttribute(SecurityAction.RequestMinimum, Name ="FullTrust")] 公共类 ImpersonationDemo { [StructLayout(LayoutKind.Sequential)] 公共结构 SECURITY_ATTRIBUTES { 公共整数长度; 公共 IntPtr lpSecurityDescriptor; 公共布尔 bInheritHandle; } 公共枚举 SECURITY_IMPERSONATION_LEVEL { 安全匿名, 安全识别, 安全模拟, 安全委托 } 公共枚举 TOKEN_TYPE { TokenPrimary = 1, 代币模拟 } [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 私有不安全静态外部 int FormatMessage(int dwFlags, ref IntPtr lpSource, int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr *Arguments); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public extern static bool CloseHandle(IntPtr 句柄); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError=true)] public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public extern static bool DuplicateTokenEx( IntPtr hExistingToken, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL 模拟级别, TOKEN_TYPE 令牌类型, 出 IntPtr phNewToken); // GetErrorMessage 格式化并返回错误信息 // 对应输入的errorCode。 公共不安全静态字符串 GetErrorMessage(int errorCode) { int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; int messageSize = 255; 字符串 lpMsgBuf = ""; int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; IntPtr ptrlpSource = IntPtr.Zero; IntPtr prtArguments = IntPtr.Zero; int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0, ref lpMsgBuf, messageSize, &prtArguments); 如果(0 == retVal) { throw new Exception("错误代码" + errorCode + ". "); } 返回 lpMsgBuf; } // 测试工具。 // 如果将此代码合并到 DLL 中,请务必要求 FullTrust。 [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")] 公共静态无效主要(字符串[]参数) { IntPtr tokenHandle = new IntPtr(0); IntPtr dupeTokenHandle = new IntPtr(0); 尝试 { 字符串用户名,机器名; // 使用指定的用户、机器和密码获取用户令牌 // 非托管 LogonUser 方法。 Console.Write("请输入要登录的机器名称:"); MachineName = Console.ReadLine(); Console.Write("请输入您希望模拟的 {0} 用户的登录名:", MachineName); 用户名 = Console.ReadLine(); Console.Write("请输入 {0} 的密码:", UserName); 常量 int LOGON32_PROVIDER_DEFAULT = 3; //此参数使 LogonUser 创建一个主令牌。 常量 int LOGON32_LOGON_INTERACTIVE = 8; tokenHandle = IntPtr.Zero; dupeTokenHandle = IntPtr.Zero; // 调用 LogonUser 以获取访问令牌的句柄。 bool returnValue = LogonUser(用户名,机器名,“mm4geata”, LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT,参考令牌句柄); Console.WriteLine("LogonUser 调用。"); 如果(假==返回值) { int ret = Marshal.GetLastWin32Error(); Console.WriteLine("LogonUser 失败,错误代码:{0}", ret); Console.WriteLine("\nError: [{0}] {1}\n", ret, GetErrorMessage(ret)); 返回; } Console.WriteLine("LogonUser 成功了吗?" + (returnValue?"Yes" : "No")); Console.WriteLine("Windows NT 令牌值:" + tokenHandle); // 检查身份。 Console.WriteLine("模拟前:" + WindowsIdentity.GetCurrent().Name); //bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle); SECURITY_ATTRIBUTES sa = 新的 SECURITY_ATTRIBUTES(); sa.bInheritHandle = true; sa.Length = Marshal.SizeOf(sa); sa.lpSecurityDescriptor = (IntPtr)0; bool retVal = DuplicateTokenEx(tokenHandle, 0x10000000, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out dupeTokenHandle); 如果(假 == retVal) { 关闭句柄(令牌句柄); Console.WriteLine("尝试复制令牌时抛出异常。"); 返回; } // 传递给以下构造函数的令牌必须 // 成为主要令牌以便将其用于模拟。 WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle); WindowsImpersonationContext impersonatedUser = newId.Impersonate(); // 检查身份。 Console.WriteLine("模拟后:" + WindowsIdentity.GetCurrent().Name); // 停止冒充用户。 模拟用户.Undo(); // 检查身份。 Console.WriteLine("撤消后:" + WindowsIdentity.GetCurrent().Name); // 释放令牌。 if (tokenHandle != IntPtr.Zero) 关闭句柄(令牌句柄); if (dupeTokenHandle != IntPtr.Zero) 关闭句柄(dupeTokenHandle); } 捕捉(异常前) { Console.WriteLine("发生异常。" + ex.Message); } } }

【讨论】:

  • 我能看到的与我的代码的唯一区别是SECURITY_ATTRIBUTES.bInheritHandle 设置为true,并且它使用TOKEN_TYPE.TokenImpersonation 而不是TokenPrimary。我稍后会试试这个,但我认为它不会有帮助。我想我需要一个主令牌来启动一个进程。
  • 不,这些都没有影响。
  • 查看我添加的链接,尤其是在示例 WindowsIdentity.Impersonate 方法中
  • 我找不到与我在示例中所做的不同的事情。您是否认为模拟没有获得正确的权限,这就是不允许启动可执行文件的原因?
  • 尝试了示例,模拟用户的不同方式,仍然得到错误代码 5。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-18
  • 1970-01-01
  • 1970-01-01
  • 2022-12-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多