【问题标题】:.Net Core Impersonation not working with Process.Start.Net Core Impersonation 不适用于 Process.Start
【发布时间】:2020-04-05 23:57:11
【问题描述】:

在 .Net Core 下使用模拟时,我似乎无法以其他用户身份启动进程。 我正在以 User1 身份运行的 Linqpad 中运行此脚本,并尝试以 User2 身份启动程序。 起初,模拟似乎有效(当前用户上的Console.Writeline()s 在RunImpersonated() 方法中从User1 正确更改为User2)。但是,该进程始终以 User1 身份运行。

这是我为验证RunImpersonated() 是否有效而进行的众多测试之一(这最初源于试图模拟当前用户的 ASP.Net Core 应用程序中的问题)。这是我能找到的最简单的可重现示例。

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
    int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);

void Main()
{
    string domainName = "myDomain";
    string userName = "User2";
    string passWord = "User2Password";

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

    // Call LogonUser to obtain a handle to an access token. 
    SafeAccessTokenHandle safeAccessTokenHandle;
    bool returnValue = LogonUser(userName, domainName, passWord,
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
        out safeAccessTokenHandle);

    if (false == returnValue)
    {
        int ret = Marshal.GetLastWin32Error();
        Console.WriteLine("LogonUser failed with error code : {0}", ret);
        throw new System.ComponentModel.Win32Exception(ret);
    }

    Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
    // Check the identity.
    Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

    // Note: if you want to run as unimpersonated, pass
    //       'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'
    WindowsIdentity.RunImpersonated(
        safeAccessTokenHandle,
        // User action
        () =>
        {
            // Check the identity.
            Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);
            Directory.GetFiles(@"C:\TMP\").Dump();
            var pi = new ProcessStartInfo
            {
                WorkingDirectory = @"C:\TMP\",
                FileName = @"C:\TMP\TestUser.exe"
            };
            var proc = Process.Start(pi);
            proc.WaitForExit();
        }
        );

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

【问题讨论】:

  • 设置ProcessStartInfo对象的UserNamePassword属性是否有效?
  • 我使用Process.Start 并在另一个凭据下运行应用程序没有任何问题。
  • @Progman 目标不是运行另一个进程,这是我们可以制作的最小可复制项目,其中在模拟上下文中运行的代码不会表现得像怀疑的那样,在实际场景中我们并没有尝试运行另一个程序,我们可以访问要模拟的令牌,但不能访问登录名/密码。目标是了解为什么这不起作用以及如何使其起作用以避免在这里提出一个很长的问题,因为这似乎是最简单的情况,但我们需要知道为什么它不能按原样起作用以及如何做到这一点通过模拟在线程级别工作,而不是进程启动
  • @RezaAghaei 虽然将凭据传递给进程很容易,但在这里我们尝试在模拟上下文中不传递任何内容的情况下执行此操作,期望它以模拟用户身份运行,而不是它以原始用户身份运行
  • 直接运行 Process.Start(@"C:\TMP\TestUser.exe");

标签: c# asp.net-core .net-core impersonation windows-identity


【解决方案1】:

如果您不指定用户名和密码,Process.Start 将使用令牌进行调用进程,而不是模拟令牌。

查看source codeProcess.Start

如果调用进程冒充另一个用户,则新进程 将令牌用于调用进程,而不是模拟令牌。 在所代表的用户的安全上下文中运行新进程 通过模拟令牌,使用 CreateProcessAsUser 或 CreateProcessWithLogonW 函数。

在不传递用户名和密码的情况下,进程始终在原始进程所有者的安全上下文中运行。如果您想在另一个用户的上下文中运行该进程:

【讨论】:

  • 我知道可以在不更改当前进程运行器的情况下运行另一个进程“as”,但这不是这个问题的目标(只是最简单的可复制示例),目标是模拟一切(无论是创建进程、远程调用其他服务器还是 COM API 等)。此示例显示了一种失败的情况,但似乎在我测试过的所有情况下都失败了。在最终场景中,我们没有用户的密码,只有模拟令牌,因此不能将其传递给 ProcessInfo。
  • 所以我们需要一个解决方案,其中以用户 A 身份运行的进程 A 可以生成以用户 B 身份运行的进程 B,而无需知道用户 B 的凭据(这是我理解的 WindowsIdentity.Impersonate 应该做的,让我们运行代码作为用户 B,只要我们有他们的令牌),它就可以显示当前用户名,但是在调用当前进程之外的任何内容时,一切似乎都是作为用户 A 而不是 B 完成的
  • @RonanThibaudau 可能令人失望,但在CreateProcess 的文档中已经提到,如果调用进程冒充另一个用户,则新进程将令牌用于调用进程,不是模拟令牌。要在模拟令牌所代表的用户的安全上下文中运行新进程,请使用CreateProcessAsUserCreateProcessWithLogonW 函数。它证实了您的观察。
  • 我可以通过用户名和密码来启动进程,甚至不需要模拟库?!?在similar issue there 中提及您的答案,并阅读secure psw
  • 是的,Process.StartSecureString psw + 用户名和域一起使用效果很好,UseShellExecute 必须为 false。越简单越好!谢谢
【解决方案2】:

有多个impersonation levels。文档指出您不能使用线程模拟令牌来启动进程 - 将始终使用主令牌:

系统使用进程的主令牌而不是 在以下情况下调用线程的模拟令牌:

如果模拟线程调用 CreateProcess 函数,新的 进程总是继承进程的主令牌。

鉴于您没有用户密码,并且您想使用模拟令牌来启动进程,很遗憾,答案是 - 您不能这样做。

您可以模拟调用者进行其他操作(RPC、COM、FS...)。

【讨论】:

  • 这实际上是个好消息,因为我们正在尝试模拟 COM,您能否确认创建一个 com 对象并在 RunImpersonated 中调用它应该将模拟流向 COM API? (我们正在尝试跟踪这是我们代码中的错误还是生产问题)。
  • 是的,模拟适用于 COM。请注意,使用默认模拟级别,您只能使用机器级别资源(本地 COM)。如果要跨越机器边界(使用 DCOM),则需要使用委托级别(需要更改 AD)。阅读有关COM impersonation and cloacking here 的更多信息。
  • @RonanThibaudau,我在同一台机器上调试应用程序时遇到了模拟问题,它就像一个魅力,但是一旦应用程序在服务器上,它并没有像我预期的那样模拟。问题是本地机器具有最高的信任度,因此当您运行服务器并调用 localhost 时,它可以使用委托并在外部模拟您的调用。当您让机器 A 调用机器 B 并且 B 尝试冒充 A 调用 C 时,C 会将调用者身份验证为 B(这都是关于机器边界的)
【解决方案3】:

这对我很有用。检查正在运行该进程的用户。有时用户不是管理员或无法模拟。

Processinfo 创建新进程。尝试 process.start,或者您可以将 exe 转换为 util dll 并在 utli.testuser 代码之类的代码中运行。使用主程序中的dll调用方法而不是exe。

   const int LOGON32_PROVIDER_DEFAULT = 0;  
    //This parameter causes LogonUser to create a primary token.     
    const int LOGON32_LOGON_INTERACTIVE = 2;  
    // Call LogonUser to obtain a handle to an access token.     
    SafeAccessTokenHandle safeAccessTokenHandle;  
    bool returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle);  
    WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () => {  
        Var impersonatedUser = WindowsIdentity.GetCurrent().Name;  
        //--- Call your Method here…….  
    });

【讨论】:

  • 如前所述,这不是我们遇到问题的地方,这已经为我们显示了模拟的用户名(这对我们没有好处,我们正在考虑模拟他们而不仅仅是获取他们的名字),它是在你说 « // 调用你的方法 » 的地方所有其他不起作用的东西:创建一个新进程,对 COM API 进行远程调用等......
  • 你不必像这样调用新进程。 var pi = 新的 ProcessStartInfo。直接运行 Process.Start(@"C:\TMP\TestUser.exe");
  • 这并没有更好的工作,pi 是在手动传递凭据时检查它是否工作的遗留物(用于传递它的登录信息,这工作但有点选项,因为我们需要模拟方法适用于我们的实际项目中的所有内容,我们只获得令牌,而不是登录名/密码)。
【解决方案4】:

不久前我使用了以下代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Windows;
using Microsoft.Win32.SafeHandles;

namespace ZZZ
{

    partial class User : IDisposable
    {

        private string m_domain;
        private string m_user;
        private string m_pass;
        private WindowsIdentity user;
        private SafeTokenHandle safeTokenHandle;
        private WindowsImpersonationContext impContext;

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(
            string lpszUsername,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out SafeTokenHandle phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool RevertToSelf();

        //For the current user
        public User()
        {
            user = WindowsIdentity.GetCurrent();
        }

        // For custom user
        public User(string domain, string user, string password, bool doImepsonate = false)
        {
            m_domain = domain;
            m_user = user;
            m_pass = password;
            if (doImepsonate) this.Impersonate();
        }


        // If it's intended to incorporate this code into a DLL, then demand FullTrust.
        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public void Impersonate()
        {
            if (impContext != null) throw new ImpersonationException();

            try
            {
                // Get the user token for the specified user, domain, and password using the unmanaged LogonUser method. 
                // The local machine name can be used for the domain name to impersonate a user on this machine.
                const int LOGON32_PROVIDER_DEFAULT = 0;
                //This parameter causes LogonUser to create a primary token. 
                const int LOGON32_LOGON_INTERACTIVE = 2;

                // Call LogonUser to obtain a handle to an access token. 
                bool returnValue = LogonUser(
                    m_user,
                    m_domain,
                    m_pass,
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT,
                    out safeTokenHandle);

                if (returnValue == false)
                {
                    int ret = Marshal.GetLastWin32Error();
                    throw new Win32Exception(ret);
                }

                using (safeTokenHandle)
                {
                    // Use the token handle returned by LogonUser. 
                    user = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
                    impContext = user.Impersonate();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception occurred:\n" + ex.Message);
            }
        }

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        private void Quit()
        {
            if (impContext == null) return;
            impContext.Undo();
            safeTokenHandle.Dispose();
        }
        #endregion

        internal IEnumerable<string> Groups
        {
            get
            {
                return user.Groups.Select(p =>
                {
                    IdentityReference ir = null;
                    try { ir = p.Translate(typeof(NTAccount)); }
                    catch { }
                    return ir == null ? null : ir.Value;
                });
            }
        }

    }

    // Win32 API part
    internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() : base(true) { }

        [DllImport("kernel32.dll")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }

    internal sealed class ImpersonationException : Exception
    {
        public ImpersonationException() : base("The user is already impersonated.") { }
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-26
    • 1970-01-01
    • 2018-09-14
    • 2020-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    相关资源
    最近更新 更多