使用Windows服务的凭据启动进程

时间:2009-03-24 15:20:18

标签: c# .net windows-services

我有一个以mydomain \ userA身份运行的Windows服务。我希望能够从服务中运行任意.exes。通常,我使用Process.Start()并且它工作正常,但在某些情况下,我想以不同的用户(mydomain \ userB)运行可执行文件。

如果我更改了ProcessStartInfo,我用来启动进程以包含凭据,我开始收到错误 - 错误对话框显示“应用程序无法正确初始化(0xc0000142)。单击确定以终止应用程序。” ,或“访问被拒绝”Win32Exception。如果我从命令行运行进程启动代码而不是在服务中运行它,则进程将使用正确的凭据开始(我已通过设置ProcessStartInfo来运行whoami.exe并捕获命令行输出来验证这一点) )。

我也尝试使用WindowsIdentity.Impersonate()进行模拟,但这没有用 - 据我理解,模拟只会影响当前线程,启动新进程会继承进程的安全描述符,而不是当前的线程。

我在一个独立的测试域中运行它,因此userA和userB都是域管理员,并且两者都在域范围内具有“作为服务登录”。

8 个答案:

答案 0 :(得分:17)

使用ProcessStartInfo启动新进程时,该进程将在启动过程的同一窗口站和桌面中启动。如果您使用不同的凭据,则用户通常没有足够的权限在该桌面上运行。如果user32.dll尝试在新进程中初始化而无法初始化错误,则会导致无法初始化错误。

要解决此问题,您必须首先检索与窗口站和桌面关联的安全描述符,并为您的用户添加适当的DACL权限,然后在新凭据下启动您的进程。

编辑:关于如何执行此操作和示例代码的详细说明在这里有点长,所以我将article与代码放在一起。

        //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))
        {
        }

答案 1 :(得分:10)

基于answer by @StephenMartin

使用Process类启动的新进程与启动进程在同一窗口站和桌面中运行。如果您使用不同的凭据运行新进程,则新进程将无权访问窗口站和桌面。什么导致错误,如0xC0000142。

以下是" compact"独立代码,授予用户访问当前窗口站和桌面的权限。它不需要AsproLock库。

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;
    }
}

答案 2 :(得分:3)

根据@Stephen Martin和Martin Prikryl的回答。

此代码可帮助您使用服务中的不同用户凭据运行流程 我现在已经优化了源代码 现在也可以删除和设置权利。

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 (var windowsAccess = new WindowsGrandAccess(accountInfo, WindowsGrandAccess.WindowStationAllAccess, WindowsGrandAccess.DesktopRightsAllAccess))
{
   ...
}

谢谢。

答案 3 :(得分:2)

这是以下症状:
- 权利不足;
- 图书馆的失败负荷;

使用Filemon检测某些拒绝访问或
WinDbg在调试器中运行应用程序并查看任何问题。

答案 4 :(得分:2)

我已经用Python重新实现了Martin Prikryl的答案,我希望有人觉得有用。

我遇到了在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}.")

我使用了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)

答案 5 :(得分:0)

您是如何设置域名,用户和密码的?您是否正确设置域以及密码(它必须使用SecureString)。

另外,您是否设置了WorkingDirectory属性?使用UserName和Password时,文档说明您必须设置WorkingDirectory属性。

答案 6 :(得分:0)

可能由服务启动的任何进程也必须具有“作为服务登录”权限。

如果您用于启动第二个流程的用户ID没有该框的管理权限,则可能就是这种情况。

一个简单的测试是更改本地安全策略,以便为用户标识“作为服务登录”并再次尝试。

编辑:在附加信息之后..

在这一个上放牧Google,看来0xc0000142与无法初始化所需的DLL有关。服务已打开,生成的流程需要什么?在任何情况下,它看起来都与启动的过程有关,而不是你是如何做的。

答案 7 :(得分:0)

我今天遇到了这个问题,我花了很多时间试图解决这个问题。我最终做的是将服务创建为交互式(使用services.msc中的允许服务与桌面交互复选框)。一旦我这样做,0xc0000142错误就消失了。