Запуск процесса с учетными данными из службы Windows - PullRequest
9 голосов
/ 24 марта 2009

У меня есть служба Windows, которая работает как mydomain \ userA. Я хочу иметь возможность запускать произвольные .exes из службы. Обычно я использую Process.Start (), и он работает нормально, но в некоторых случаях я хочу запустить исполняемый файл от имени другого пользователя (mydomain \ userB).

Если я изменяю ProcessStartInfo, который я использую, чтобы запустить процесс, чтобы включить учетные данные, я начинаю получать ошибки - либо диалоговое окно с сообщением об ошибке, которое говорит: «Не удалось правильно инициализировать приложение (0xc0000142). Нажмите« ОК », чтобы завершить работу приложения». или «Отказано в доступе» Win32Exception. Если я запускаю код запуска процесса из командной строки, а не запускаю его в службе, процесс запускается с использованием правильных учетных данных (я подтвердил это, установив ProcessStartInfo для запуска whoami.exe и захватив выходные данные командной строки ).

Я также пробовал олицетворение с помощью WindowsIdentity.Impersonate (), но это не сработало - насколько я понимаю, олицетворение влияет только на текущий поток, а запуск нового процесса наследует дескриптор безопасности процесса, а не текущий нить.

Я запускаю это в изолированном тестовом домене, поэтому и userA, и userB являются администраторами домена, и оба имеют право входа в систему как сервис по всему домену.

Ответы [ 7 ]

17 голосов
/ 24 марта 2009

Когда вы запускаете новый процесс с помощью ProcessStartInfo, процесс запускается на той же оконной станции и рабочем столе, что и процесс запуска. Если вы используете разные учетные данные, то у пользователя, как правило, не будет достаточно прав для запуска на этом рабочем столе. Ошибка инициализации ошибки возникает, когда user32.dll пытается инициализировать в новом процессе и не может.

Чтобы обойти это, вы должны сначала получить дескрипторы безопасности, связанные с оконной станцией и рабочим столом, и добавить соответствующие разрешения для DACL для вашего пользователя, а затем запустить процесс под новыми учетными данными.

РЕДАКТИРОВАТЬ: подробное описание того, как это сделать, и пример кода был немного длинным, поэтому я собрал статью с кодом.

        //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))
        {
        }
10 голосов
/ 06 июня 2015

На основе ответа @ StephenMartin .

Новый процесс, запущенный с использованием класса Process, выполняется на той же оконной станции и рабочем столе, что и процесс запуска. Если вы запускаете новый процесс с использованием других учетных данных, то у нового процесса не будет разрешений для доступа к оконной станции и рабочему столу. Что приводит к ошибкам типа 0xC0000142.

Ниже приведен «компактный» автономный код, предоставляющий пользователю доступ к текущей оконной станции и рабочему столу. Для этого не требуется библиотека 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;
    }
}
3 голосов
/ 25 апреля 2017

На основании ответа @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: /627389/zapusk-protsessa-s-uchetnymi-dannymi-iz-sluzhby-windows
    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))
{
   ...
}

Спасибо.

2 голосов
/ 24 марта 2009

Это симптом:
- недостаточные права;
- сбой загрузки библиотеки;

Используйте Filemon для обнаружения отказа в доступе или
WinDbg для запуска приложения в отладчике и просмотра любой проблемы.

0 голосов
/ 26 мая 2009

У меня была эта проблема сегодня, и я потратил довольно много времени, пытаясь ее решить. Я закончил тем, что создал сервис как интерактивный (с помощью сервиса Разрешить взаимодействие с рабочим столом в services.msc). Как только я это сделал, ошибки 0xc0000142 исчезли.

0 голосов
/ 24 марта 2009

Возможно, что любой процесс, запущенный службой, также должен иметь привилегию «Вход в систему как служба».

Если идентификатор пользователя, который вы используете для запуска второго процесса, не имеет административных прав на ящик, это может иметь место.

Простым тестом было бы изменить локальную политику безопасности, указав идентификатор пользователя «Вход в систему как сервис» и повторить попытку.

Редактировать: после дополнительной информации ..

По этому вопросу, похоже, что Google 0xc0000142 не может инициализировать необходимую DLL. Есть ли что-то, что служба открыла для нужд порожденного процесса? В любом случае, похоже, что это связано с процессом, который был запущен, а не с тем, как вы это делаете.

0 голосов
/ 24 марта 2009

Как вы настраиваете домен, пользователя и пароль? Правильно ли вы устанавливаете домен и пароль (для этого необходимо использовать SecureString).

Кроме того, вы устанавливаете свойство WorkingDirectory? При использовании имени пользователя и пароля в документации указывается, что необходимо установить свойство WorkingDirectory.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...