Impersonation in C#

What is impersonation?

Impersonation is one of the most useful mechanisms in Windows security. It is also fragile and easy to misuse. Careful use of impersonation can lead to a secure, easy-to-administer application. Misuse can open gaping security holes. After an application authenticates a user, the application can take on that user’s identity through impersonation. Impersonation happens on a thread-by-thread basis to allow for concurrency, which is important for multithreaded servers as each thread might be servicing a different client.


In simple words

 “Impersonation is the process of executing code in the context of another user identity” 

The reason for doing this is to perform tasks that the current user context of an application is not allowed to do. For example, if the application is running in a restricted account and if it needs administrative privileges to perform certain actions, impersonations is the right technique. Of course it is possible to grant the user more privileges, but usually this is a bad idea (due to security constraints) or impossible (e.g. no administrative access to a machine to set privileges for the user).

Impersonate functionality which is implemented in C#

The System.Security.Principal namespace and System.Runtime.InteropServices has classes that can be used for impersonation.

 

    /// 
    ///     class to impersonate a user
    /// 
    public static class Impersonator
    {
        public enum EoAuthnCap
        {
            None = 0x00,
            MutualAuth = 0x01,
            StaticCloaking = 0x20,
            DynamicCloaking = 0x40,
            AnyAuthority = 0x80,
            MakeFullSIC = 0x100,
            Default = 0x800,
            SecureRefs = 0x02,
            AccessControl = 0x04,
            AppID = 0x08,
            Dynamic = 0x10,
            RequireFullSIC = 0x200,
            AutoImpersonate = 0x400,
            NoCustomMarshal = 0x2000,
            DisableAAA = 0x1000
        }

        public enum RpcAuthnLevel
        {
            Default = 0,
            None = 1,
            Connect = 2,
            Call = 3,
            Pkt = 4,
            PktIntegrity = 5,
            PktPrivacy = 6
        }

        public enum RpcImpLevel
        {
            Default = 0,
            Anonymous = 1,
            Identify = 2,
            Impersonate = 3,
            Delegate = 4
        }

        private const int LOGON32_PROVIDER_DEFAULT = 0;
        private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
        private const int SECURITY_IMPERSONATION_LEVEL = 2;

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

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DuplicateToken(IntPtr existingTokenHandle, int impersonationLevel,
            ref IntPtr duplicateTokenHandle);

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

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

        [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);

        [DllImport("Userenv.dll", CallingConvention =
            CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool UnloadUserProfile
            (IntPtr hToken, IntPtr lpProfileInfo);

        [DllImport("ole32.dll")]
        public static extern int CoInitializeSecurity(IntPtr pVoid, int
                cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1, RpcAuthnLevel level,
            RpcImpLevel impers, IntPtr pAuthList, EoAuthnCap dwCapabilities, IntPtr
                pReserved3);

        public static void RunOperationAsUser(Action operation, string userName, string domain, string password)
        {
            var token = IntPtr.Zero;
            var dupToken = IntPtr.Zero;

            //Impersonate the user
            if (LogonUser(userName, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
                ref token))
                if (DuplicateToken(token, SECURITY_IMPERSONATION_LEVEL, ref dupToken))
                {
                    var newIdentity = new WindowsIdentity(dupToken);
                    var impersonatedUser = newIdentity.Impersonate();

                    var retCode = CoInitializeSecurity(IntPtr.Zero, -1, IntPtr.Zero, IntPtr.Zero,
                        RpcAuthnLevel.PktPrivacy, RpcImpLevel.Impersonate, IntPtr.Zero, EoAuthnCap.DynamicCloaking,
                        IntPtr.Zero);

                    if (impersonatedUser != null)
                    {
                        var username = WindowsIdentity.GetCurrent(TokenAccessLevels.MaximumAllowed).Name;
                        var sid = WindowsIdentity.GetCurrent(TokenAccessLevels.MaximumAllowed).User.Value;

                        var profileInfo = new ProfileInfo();
                        profileInfo.dwSize = Marshal.SizeOf(profileInfo);
                        profileInfo.lpUserName = userName;
                        profileInfo.dwFlags = 1;

                        var loadSuccess = LoadUserProfile(dupToken, ref profileInfo);
                    }

                    operation();

                    impersonatedUser.Undo();
                }

            if (token != IntPtr.Zero) CloseHandle(token);
            if (dupToken != IntPtr.Zero)
                try
                {
                    CloseHandle(token);
                }
                catch
                {
                }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ProfileInfo
        {
            /// Specifies the size of the structure, in bytes.
            public int dwSize;

            /// This member can be one of the following flags: 
            /// PI_NOUI or PI_APPLYPOLICY
            public int dwFlags;

            /// Pointer to the name of the user.
            /// This member is used as the base name of the directory 
            /// in which to store a new profile.
            public string lpUserName;

            /// Pointer to the roaming user profile path.
            /// If the user does not have a roaming profile, this member can be NULL.
            public string lpProfilePath;

            /// Pointer to the default user profile path. This member can be NULL.
            public string lpDefaultPath;

            /// Pointer to the name of the validating domain controller, in NetBIOS format.
            /// If this member is NULL, the Windows NT 4.0-style policy will not be applied.
            public string lpServerName;

            /// Pointer to the path of the Windows NT 4.0-style policy file. 
            /// This member can be NULL.
            public string lpPolicyPath;

            /// Handle to the HKEY_CURRENT_USER registry key.
            public IntPtr hProfile;
        }
    }

 

We can call the Impersonate function like below

 

Impersonator.RunOperationAsUser(() =>{
//Add the actions here
},userName,domain,password);

Comments

Popular Posts