CreateProcessAsUser:服务获取“5:拒绝访问”尝试访问网络共享

时间:2013-01-22 16:40:21

标签: c# networking windows-services impersonation

我使用Windows服务中的CreateProcessAsUser来为当前活动用户启动应用程序。到目前为止,它适用于本地驱动器上的应用程序。

但是,如果网络共享上存在可执行文件,则当我使用完整服务器名称(\ myserver \ path \ app.exe)时,该服务会生成5:ERROR_ACCESS_DENIED。如果我使用映射驱动器(P:\ path \ app.exe),我也可以生成2:ERROR_FILE_NOT_FOUND。

我可以从资源管理器中启动应用程序。听起来我无法获得正确的令牌副本,因为服务无法在服务器上正确冒充我。

我尝试了各种帖子中的几个不同的CreateProcessAsUser实现,但无济于事。这对我来说是全新的(迷幻的)东西,坦率地说,我迫不及待想要回到.NET :)我猜这个有问题的线就在这里:

DuplicateTokenEx(
    hUserToken,
    (Int32)MAXIMUM_ALLOWED,
    ref sa,
    (Int32)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
    (Int32)TOKEN_TYPE.TokenPrimary,
    ref hUserTokenDup);

CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true);

Int32 dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;

PROCESS_INFORMATION pi;
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";

CreateProcessAsUser(hUserTokenDup,    // client's access token
    null,                             // file to execute
    commandLine,                      // command line
    ref sa,                           // pointer to process SECURITY_ATTRIBUTES
    ref sa,                           // pointer to thread SECURITY_ATTRIBUTES
    false,                            // handles are not inheritable
    dwCreationFlags,                  // creation flags
    pEnv,                             // pointer to new environment block 
    workingDirectory,                 // name of current directory 
    ref si,                           // pointer to STARTUPINFO structure
    out pi);                          // receives information about new process

以下是完整的示例代码,我想它可能很有用:

using System;
using System.Text;
using System.Security;
using System.Management;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Win32
{
    public class Win32API
    {
        [StructLayout(LayoutKind.Sequential)]
        struct SECURITY_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public Boolean bInheritHandle;
        }

        enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation = 2
        }

        [StructLayout(LayoutKind.Sequential)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public String lpReserved;
            public String lpDesktop;
            public String lpTitle;
            public UInt32 dwX;
            public UInt32 dwY;
            public UInt32 dwXSize;
            public UInt32 dwYSize;
            public UInt32 dwXCountChars;
            public UInt32 dwYCountChars;
            public UInt32 dwFillAttribute;
            public UInt32 dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public UInt32 dwProcessId;
            public UInt32 dwThreadId;
        }

        enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3,
        }

        const UInt32 MAXIMUM_ALLOWED = 0x2000000;
        const Int32 CREATE_UNICODE_ENVIRONMENT = 0x00000400;
        const Int32 NORMAL_PRIORITY_CLASS = 0x20;
        const Int32 CREATE_NEW_CONSOLE = 0x00000010;

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern Boolean CloseHandle(IntPtr hSnapshot);

        [DllImport("kernel32.dll")]
        public static extern UInt32 WTSGetActiveConsoleSessionId();

        [DllImport("Wtsapi32.dll")]
        static extern UInt32 WTSQueryUserToken(UInt32 SessionId, ref IntPtr phToken);

        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        extern static Boolean CreateProcessAsUser(
            IntPtr hToken,
            String lpApplicationName,
            String lpCommandLine,
            ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            Boolean bInheritHandle,
            Int32 dwCreationFlags,
            IntPtr lpEnvironment,
            String lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        extern static Boolean DuplicateTokenEx(
            IntPtr ExistingTokenHandle,
            UInt32 dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            Int32 TokenType,
            Int32 ImpersonationLevel,
            ref IntPtr DuplicateTokenHandle);

        [DllImport("userenv.dll", SetLastError = true)]
        static extern Boolean CreateEnvironmentBlock(
            ref IntPtr lpEnvironment,
            IntPtr hToken,
            Boolean bInherit);

        [DllImport("userenv.dll", SetLastError = true)]
        static extern Boolean DestroyEnvironmentBlock(IntPtr lpEnvironment);

        /// <summary>
        /// Creates the process in the interactive desktop with credentials of the logged in user.
        /// </summary>
        public static Boolean CreateProcessAsUser(String commandLine, String workingDirectory, out StringBuilder output)
        {
            Boolean processStarted = false;
            output = new StringBuilder();

            try
            {
                UInt32 dwSessionId = WTSGetActiveConsoleSessionId();
                output.AppendLine(String.Format("Active console session Id: {0}", dwSessionId));

                IntPtr hUserToken = IntPtr.Zero;
                WTSQueryUserToken(dwSessionId, ref hUserToken);

                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);

                IntPtr hUserTokenDup = IntPtr.Zero;
                DuplicateTokenEx(
                    hUserToken,
                    (Int32)MAXIMUM_ALLOWED,
                    ref sa,
                    (Int32)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (Int32)TOKEN_TYPE.TokenPrimary,
                    ref hUserTokenDup);


                if (hUserTokenDup != IntPtr.Zero)
                {
                    output.AppendLine(String.Format("DuplicateTokenEx() OK (hToken: {0})", hUserTokenDup));

                    Int32 dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

                    IntPtr pEnv = IntPtr.Zero;
                    if (CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
                    {
                        dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
                        output.AppendLine(String.Format("CreateEnvironmentBlock() success."));
                    }
                    else
                    {
                        output.AppendLine(String.Format("CreateEnvironmentBlock() FAILED (Last Error: {0})", Marshal.GetLastWin32Error()));
                        pEnv = IntPtr.Zero;
                    }

                    // Launch the process in the client's logon session.
                    PROCESS_INFORMATION pi;

                    STARTUPINFO si = new STARTUPINFO();
                    si.cb = Marshal.SizeOf(si);
                    si.lpDesktop = "winsta0\\default";

                    output.AppendLine(String.Format("CreateProcess (Path:{0}, CurrDir:{1})", commandLine, workingDirectory));

                    if (CreateProcessAsUser(hUserTokenDup,    // client's access token
                            null,                // file to execute
                            commandLine,        // command line
                            ref sa,                // pointer to process SECURITY_ATTRIBUTES
                            ref sa,                // pointer to thread SECURITY_ATTRIBUTES
                            false,                // handles are not inheritable
                            dwCreationFlags,    // creation flags
                            pEnv,                // pointer to new environment block 
                            workingDirectory,    // name of current directory 
                            ref si,                // pointer to STARTUPINFO structure
                            out pi                // receives information about new process
                        ))
                    {
                        processStarted = true;
                        output.AppendLine(String.Format("CreateProcessAsUser() OK (PID: {0})", pi.dwProcessId));
                    }
                    else
                    {
                        output.AppendLine(String.Format("CreateProcessAsUser() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
                    }

                    if (DestroyEnvironmentBlock(pEnv))
                    {
                        output.AppendLine("DestroyEnvironmentBlock: Success");
                    }
                    else
                    {
                        output.AppendLine(String.Format("DestroyEnvironmentBlock() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
                    }
                }
                else
                {
                    output.AppendLine(String.Format("DuplicateTokenEx() failed (Last Error: {0})", Marshal.GetLastWin32Error()));
                }
                CloseHandle(hUserTokenDup);
                CloseHandle(hUserToken);
            }
            catch (Exception ex)
            {
                output.AppendLine("Exception occurred: " + ex.Message);
            }
            return processStarted;
        }
    }
}

它适用于这样的本地可执行文件:

StringBuilder result = new StringBuilder();
Win32API.CreateProcessAsUser(@"C:\Windows\notepad.exe", @"C:\Windows\", out result);

我的问题:为了使用重复令牌正确访问网络共享,需要调整哪些内容?

1 个答案:

答案 0 :(得分:1)

当您对允许访客访问的共享(即没有用户名/密码)使用此命令时,该命令可以正常工作,但是当您对需要身份验证的共享使用它时,它不起作用。

UI调用会涉及重定向器,它会自动建立与执行所需的远程服务器的连接。

解决方法,请注意,但不是真正的解决方案是使用基于cmd的中继来获取可执行文件,因此对于命令行,您可以使用以下内容:

CreateProcessAsUser(@"cmd /c ""start \\server\share\binary.exe""", @"C:\Windows", out result);

然后使用以下命令将startupinfo更改为SW_HIDE cmd窗口。

si.cb = Marshal.SizeOf(si);
si.lpDesktop = "winsta0\\default";
si.dwFlags = 0x1; // STARTF_USESHOWWINDOW
si.wShowWindow = 0; // SW_HIDE

在启动命令之前,cmd调用是完全进入用户环境的垫片 - 这将利用所有凭据来访问服务器。

请注意,您可能需要一些逻辑来阻止SW_HIDE直接调用的应用程序(例如,在commandLine字符串的开头检查cmd?)