Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

use Process.Start while Impersonating (Window Application)

I'm trying to use Process.Start() under Impersonation, i have google for few days, most answer i come across was under ASP.net, but I'm developing for Window Application, so I'm having difficulty to find the root cause.

This is my impersonate code

     private void impersonateValidUser(string userName, string domain, string password)
        {
            WindowsIdentity tempWindowsIdentity = null;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;            
            if ( LogonUser( userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
                {
                    tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
                    mImpersonationContext = tempWindowsIdentity.Impersonate();
                }
            }
        } 

and i'm trying to open document through my program (none .exe, such as .txt, .doc)

    using (new Impersonator(DomainUserID, Domain, Password))
        Process.Start(filePath);

So far I'm able to detect the directory/file with the impersonate user, which suppose to be invisible to my current login user since i did not grant it the access. But whenever i try to open the document, i get error

    System.ComponentModel.Win32Exception (0x80004005): Access is denied

Please correct me if I'm wrong, so in this scenario, I'm not suppose to set the UseShellExecute to false (and processStartInfo with username and password input too?) because it's for executable file(?), and i do come across with CreateProcessAsUser function as well, and this function doesn't seems to be applicable to my case too, from the example i read it's for the .exe file too.

Would be appreciate if anyone could enlightenment me.

update: impersonate class

public class Impersonator : IDisposable
{
    #region P/Invoke

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser( string lpszUserName,
                                         string lpszDomain,
                                         string lpszPassword,
                                         int dwLogonType,
                                         int dwLogonProvider,
                                         ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken( IntPtr hToken,
                                              int impersonationLevel,
                                              ref IntPtr hNewToken);

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

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

    #region Constants
    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0; 
    #endregion

    #region Attributes
    private WindowsImpersonationContext mImpersonationContext = null;
    #endregion

    #region Public methods.

    public Impersonator( string userName, string domainName, string password)
    {
        impersonateValidUser(userName, domainName, password);
    }

    #endregion

    #region IDisposable member.
    public void Dispose()
    {
        undoImpersonation();
    }
    #endregion

    #region Private member.

    private void impersonateValidUser(string userName, string domain, string password)
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if ( RevertToSelf() )
            {
                if ( LogonUser( userName, domain, password,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                {
                    if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
                    {
                        tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
                        mImpersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception( Marshal.GetLastWin32Error() );
                    }
                }
                else
                {
                    throw new Win32Exception( Marshal.GetLastWin32Error() );
                }
            }
            else
            {
                throw new Win32Exception( Marshal.GetLastWin32Error() );
            }
        }
        finally
        {
            if ( token != IntPtr.Zero )
            {
                CloseHandle( token );
            }
            if ( tokenDuplicate != IntPtr.Zero )
            {
                CloseHandle( tokenDuplicate );
            }
        }
    }

    /// <summary>
    /// Reverts the impersonation.
    /// </summary>
    private void undoImpersonation()
    {
        if ( mImpersonationContext != null )
        {
            mImpersonationContext.Undo();
        }   
    }

    #endregion
}
like image 440
ragk Avatar asked Dec 17 '15 09:12

ragk


2 Answers

You can't use UseShellExecute = true when impersonating. This is related to the way how shell execution works in Windows. Basically everything is passed to the shell which looks up how to handle the verb ("open" in your case) and then starts the application under the user owning the shell, which is not the impersonated user - the impersonated user doesn't actually have a shell if there is no session!

Although you use a different mechanism for impersonating a user the documentation for UseShellExecute still applies in your case:

UseShellExecute must be false if the UserName property is not null or an empty string, or an InvalidOperationException will be thrown when the Process.Start(ProcessStartInfo) method is called.

To solve this issue it might be the easiest to look up the registered application yourself as described in this answer: Finding the default application for opening a particular file type on Windows. With the path to the associated application you can then start the executable as the other user.

like image 66
Dirk Vollmar Avatar answered Nov 07 '22 17:11

Dirk Vollmar


Here is the complete class I use to impersonate user:

/// <summary>
/// Provide a context to impersonate operations.
/// </summary>
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonate : IDisposable
{
    /// <summary>
    /// Initialize a new instance of the ImpersonateValidUser class with the specified user name, password, and domain.
    /// </summary>
    /// <param name="userName">The user name associated with the impersonation.</param>
    /// <param name="password">The password for the user name associated with the impersonation.</param>
    /// <param name="domain">The domain associated with the impersonation.</param>
    /// <exception cref="ArgumentException">If the logon operation failed.</exception>
    public Impersonate(string userName, SecureString password, string domain)
    {
        ValidateParameters(userName, password, domain);
        WindowsIdentity tempWindowsIdentity;
        IntPtr userAccountToken = IntPtr.Zero;
        IntPtr passwordPointer = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if (Kernel32.RevertToSelf())
            {
                // Marshal the SecureString to unmanaged memory.
                passwordPointer = Marshal.SecureStringToGlobalAllocUnicode(password);

                if (Advapi32.LogonUser(userName, domain, passwordPointer, LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT, ref userAccountToken))
                {
                    if (Advapi32.DuplicateToken(userAccountToken, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            return;
                        }
                    }
                }
            }
        }
        finally
        {
            // Zero-out and free the unmanaged string reference.
            Marshal.ZeroFreeGlobalAllocUnicode(passwordPointer);

            // Close the token handle.
            if (userAccountToken != IntPtr.Zero)
                Kernel32.CloseHandle(userAccountToken);
            if (tokenDuplicate != IntPtr.Zero)
                Kernel32.CloseHandle(tokenDuplicate);
        }

        throw new ArgumentException(string.Format("Logon operation failed for userName {0}.", userName));
    }

    /// <summary>
    /// Reverts the user context to the Windows user represented by the WindowsImpersonationContext.
    /// </summary>
    public void UndoImpersonation()
    {
        impersonationContext.Undo();
    }

    /// <summary>
    /// Releases all resources used by <see cref="Impersonate"/> :
    /// - Reverts the user context to the Windows user represented by this object : <see cref="System.Security.Principal.WindowsImpersonationContext"/>.Undo().
    /// - Dispose the <see cref="System.Security.Principal.WindowsImpersonationContext"/>.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (impersonationContext != null)
            {
                //UndoImpersonation();
                impersonationContext.Dispose();
                impersonationContext = null;
            }
        }
    }

    private void ValidateParameters(string userName, SecureString password, string domain)
    {
        if (string.IsNullOrWhiteSpace(userName))
        {
            throw new ArgumentNullException("userName");
        }
        if (password == null || password.Length == 0)
        {
            throw new ArgumentNullException("password");
        }
        if (string.IsNullOrWhiteSpace(userName))
        {
            throw new ArgumentNullException("domain");
        }
    }

    private WindowsImpersonationContext impersonationContext;

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;
}

how to convert a string to a secure string :

var secure = new SecureString();
foreach (char c in "myclearpassword")
{
    secure.AppendChar(c);
}

SO You can use it like that:

using (var imp = new Impersonate(DomainUserID, SecurePassword, Domain))
{
    Process.Start(filePath);
}
like image 1
Thomas Avatar answered Nov 07 '22 16:11

Thomas