简体   繁体   中英

.Net Core Impersonation not working with Process.Start

I can't seem to start a process as another user when using impersonation under .Net Core. I'm running this script in Linqpad running as User1 and trying to launch a program as User2. At first, the impersonation seems to work (the Console.Writeline() s on the current user change correctly from User1 to User2 in the RunImpersonated() Method). However, the process always runs as User1.

This is one of many tests I'm doing to validate that RunImpersonated() works (this originally stems from issues in an ASP.Net Core App trying to impersonate the current user). This is the simplest reproducible example I could find.

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

If you don't specify username and password, Process.Start will uses the token for the calling process, not the impersonation token.

Looking into source code of Process.Start :

If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.

Without passing username and password the process always runs on security context of the original process owner. If you want to run the process under the context of another user:

A while back I used the following code:

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.") { }
    }

}

This works great for me. Check the user who is running the process. Sometime the user is not administrator or cannot impersonate.

Processinfo creates new process. Try process.start, Or you can convert exe to util dll and run inside code like utli.testuser code. Use dll call method from main program and not 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…….  
    });

There are multiple impersonation levels . Documentation states that you cannot use thread impersonation token to start the process - primary token will always be used:

The system uses the primary token of the process rather than the impersonation token of the calling thread in the following situations:

If an impersonating thread calls the CreateProcess function, the new process always inherits the primary token of the process.

Given you don't have user's password and you want to use impersonation token instead to start a process, unfortunately, the answer is - you can't do that.

You can impersonate caller for other operations though (RPC, COM, FS...).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM