从Windows服务的特定凭据启动进程

时间:2014-02-11 16:59:17

标签: c# winapi process

我已经花了几天时间来解决这个问题,尽管网上有很多不同的例子,这是一个棘手的问题,我不能让他们在我的场景中工作。

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

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

问题是,当服务尝试启动该过程时,它会从ERROR_ACCESS_DENIED (5)方法获取CreateProcessAsUser错误代码。

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

代码太大了,不能在这里展示,所以我把它放在其他地方......

ProcessHelper http://pastie.org/private/y7idu3nw4xv1fxzeizbn9g

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

ImpersonationContext http://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

2 个答案:

答案 0 :(得分:1)

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

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

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;

[assembly:SecurityPermissionAttribute(SecurityAction.RequestMinimum,UnmanagedCode=true)]
[assembly:PermissionSetAttribute(SecurityAction.RequestMinimum, Name ="FullTrust")] 
public class ImpersonationDemo 
{

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    } 

    [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)]
    private unsafe static extern 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 handle);

    [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 ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out IntPtr phNewToken);

    // GetErrorMessage formats and returns an error message
    // corresponding to the input errorCode.
    public unsafe static string GetErrorMessage(int errorCode)
    {
        int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
        int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
        int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

        int messageSize = 255;
        String 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);
        if (0 == retVal)
        {
            throw new Exception("Failed to format message for error code " + errorCode + ". ");
        }

    return lpMsgBuf;
    }

    // Test harness.
    // If you incorporate this code into a DLL, be sure to demand FullTrust.
    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public static void Main(string[] args)
    {
        IntPtr tokenHandle = new IntPtr(0);
        IntPtr dupeTokenHandle = new IntPtr(0);
        try
        {
            string UserName, MachineName;

            // Get the user token for the specified user, machine, and password using the
            // unmanaged LogonUser method.

            Console.Write("Enter the name of a machine on which to log on: ");
            MachineName = Console.ReadLine();

            Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", MachineName);
            UserName = Console.ReadLine();

            Console.Write("Enter the password for {0}: ", UserName);

            const int LOGON32_PROVIDER_DEFAULT = 3;
            //This parameter causes LogonUser to create a primary token.
            const int LOGON32_LOGON_INTERACTIVE = 8;

            tokenHandle = IntPtr.Zero;
            dupeTokenHandle = IntPtr.Zero;

            // Call LogonUser to obtain a handle to an access token.
            bool returnValue = LogonUser(UserName, MachineName, "mm4geata", 
                LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle);

            Console.WriteLine("LogonUser called.");

            if (false == returnValue)
            {
                int ret = Marshal.GetLastWin32Error();
                Console.WriteLine("LogonUser failed with error code : {0}", ret);
                Console.WriteLine("\nError: [{0}] {1}\n", ret, GetErrorMessage(ret));

                return;
            }

            Console.WriteLine("Did LogonUser Succeed? " + (returnValue? "Yes" : "No"));
            Console.WriteLine("Value of Windows NT token: " + tokenHandle);

            // Check the identity.
            Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

            //bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle);

            SECURITY_ATTRIBUTES sa = new 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); 


            if (false == retVal)
            {
                CloseHandle(tokenHandle);
                Console.WriteLine("Exception thrown in trying to duplicate token.");
                return;
            }


            // The token that is passed to the following constructor must
            // be a primary token in order to use it for impersonation.
            WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
            WindowsImpersonationContext impersonatedUser = newId.Impersonate();

            // Check the identity.
            Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);

            // Stop impersonating the user.
            impersonatedUser.Undo();

            // Check the identity.
            Console.WriteLine("After Undo: " + WindowsIdentity.GetCurrent().Name);

            // Free the tokens.
            if (tokenHandle != IntPtr.Zero)
            CloseHandle(tokenHandle);
            if (dupeTokenHandle != IntPtr.Zero)
            CloseHandle(dupeTokenHandle);
        }
        catch(Exception ex)
        {
            Console.WriteLine("Exception occurred. " + ex.Message);
        }

    }
}

答案 1 :(得分:0)

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

ProcessHelper http://pastie.org/private/dlkytj8rbigs8ixwtg

TokenImpersonationContext http://pastie.org/private/nu3pvpghoea6pwwlvjuq

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

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

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