LoadUserProfile not actually loading profile?

3

This code, run from IIS under the app pool account, can run an executable like notepad.exe or some custom simple .NET app. I can even write to a file from the guest account/app. But accessing the registry (such as Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main", "Local Page", null);) from the app results in a generic .NET APP CRASH (0xC0000142).

I'm pretty sure that the LoadUserProfile() is what is failing to have an effect.

I've tried several variations, including SetUserObjectSecurity for the "current" window station and desktop (to an EXPLICT_ACCESS with WINSTA_ALL_ACCESS | READ_CONTROL and GENERIC_ALL respectively but this has no noticeable effect). I saw this access rights to the window station and desktop suggested elsewhere.

I've tried variations with LogonUserEx() or CreateProcessWithLogon() (instead of LogonUser() and LoadProfile()) and CreateProcessWithToken(...LOGON_WITH_PROFILE...), CreateProcessAsUser().

One thing I don't understand is why LoadUserProfile() doesn't seem to have any association with a logon/session/window station/desktop/process. What are we loading the profile into? Maybe I'm loading the profile, but not into where the target can access it?

I have logon as batch rights for the target account and all kinds of rights for the service account including administrator, act as part of the OS, adjust memory quotas, replace tokens, backup, and restore. I suspect that UAC or a similar mechanism is stripping some of these rights from the app pool account when it is created.

Anyway, the big question is how do we successfully load profile from a service?

The targeted apps run fine when invoked by IIS for execution by the same user account using the built-in System.Diagnostics.Process, but fail when a different user account is specified. Apparently "Process.Start internally calls CreateProcessWithLogonW(CPLW) when credentials are specified, which cannot be called from a Windows Service Environment."

uint exitCode;
IntPtr userToken = IntPtr.Zero;
IntPtr userProfile = IntPtr.Zero;

try
{
    if (!Native.LogonUser(
        username,
        domain,
        password,
        Native.LOGON32_LOGON_BATCH,
        Native.LOGON32_PROVIDER_DEFAULT,
        ref userToken))
    {
        var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
        throw new Exception("LogonUser failed: " + win32Ex.Message, win32Ex);
    }

    Native.PROFILEINFO profileInfo = new Native.PROFILEINFO();
    profileInfo.dwSize = Marshal.SizeOf(profileInfo);
    profileInfo.lpUserName = username;

    if (!Native.LoadUserProfile(userToken, ref profileInfo))
    {
        var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
        throw new Exception("LoadUserProfile failed: " + win32Ex.Message, win32Ex);
    }

    Native.STARTUPINFO startUpInfo = default(Native.STARTUPINFO);
    startUpInfo.cb = Marshal.SizeOf(startUpInfo);
    startUpInfo.lpDesktop = string.Empty;

    Native.PROCESS_INFORMATION processInfo = default(Native.PROCESS_INFORMATION);

    try
    {
        if (!Native.CreateProcessAsUserW(
            userToken,
            command,
            // CreateProcessAsUser() doesn't include the executable name in the args as other mechanisms do,
            // and so when you read them in on the other side (which skips args[0] by convention) you'll be missing
            // your expected first argument!
            string.Format("\"{0}\" {1}", command, arguments),
            IntPtr.Zero,
            IntPtr.Zero,
            true,
            0,
            IntPtr.Zero,
            null,
            ref startUpInfo,
            out processInfo))
        {
            var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
            throw new Exception("CreateProcessAsUserW failed: " + win32Ex.Message, win32Ex);
        }

        Native.WaitForSingleObject(processInfo.hProcess, Native.INFINITE);

        if (!Native.GetExitCodeProcess(processInfo.hProcess, out exitCode))
        {
            var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
            throw new Exception("GetExitCodeProcess failed: " + win32Ex.Message, win32Ex);
        }
    }
    finally
    {
        Native.CloseHandle(processInfo.hThread);
        Native.CloseHandle(processInfo.hProcess);
    }
}
finally
{
    if (userProfile != IntPtr.Zero) Native.UnloadUserProfile(userToken, userProfile);
    if (userToken != IntPtr.Zero) Native.CloseHandle(userToken);
}

ViewBag.Message = string.Format("VersionB ran to the end with exit code ({0})", exitCode);

return View("Index");

The LogonUserEx crashes IIS hard with "Access Violation Exception" with every variation I tried suggesting that the pinvoke signature is very wrong. (maybe I had it close once, or maybe that it worked was just (un)lucky).

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] // to invoke the 'W' variant, I tried some variations there AND specifed MarshallAs on the strings.
internal static extern bool LogonUserEx(
    string lpszUsername,
    string lpszDomain,
    string lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    ref IntPtr phToken,
    ref IntPtr ppLogonSid, // I tried these as out and no decoration 
    ref IntPtr ppProfileBuffer, // I tried null, IntPtr.zero
    ref IntPtr pdwProfileLength, //
    ref IntPtr pQuotaLimits
);
c#
winapi
iis
com-interop
asked on Stack Overflow Dec 11, 2014 by Jason Kleban • edited May 23, 2017 by Community

1 Answer

1

I am aware of your problems because i am/was facing the same odd behavior on a Service. I created some code that does the trick IF the user you try to Impersonate, has Admin rights ( nothing else is request )

The Service must be started as the LocalSystem Account to get the Rights to Impersonate users. like

Act as part of the System
Duplicate Token

public class ImpersonateUserClass
{
    public static IntPtr ImpersonateUser(string sUsername, string sDomain, string sPassword)
    {
        // initialize tokens
        var pExistingTokenHandle = new IntPtr(0);
        pExistingTokenHandle = IntPtr.Zero;
        IntPtr token = IntPtr.Zero;

        // if domain name was blank, assume local machine
        if (sDomain == "")
        {
            sDomain = Environment.MachineName;
        }

        try
        {
            unsafe
            {
                const int LOGON32_PROVIDER_DEFAULT = 0;
                bool bImpersonated = NativMethodes.LogonUser(sUsername, sDomain, sPassword,
                                     (int) NativMethodes.LOGON_TYPE.LOGON32_LOGON_BATCH, 
                                     LOGON32_PROVIDER_DEFAULT,
                                     out pExistingTokenHandle);

                // did impersonation fail?
                if (false == bImpersonated)
                {
                    throw new Win32Exception("bImpersonated");
                }

                bool bRetVal = NativMethodes.DuplicateTokenEx(pExistingTokenHandle,
                    0,
                    null,
                    NativMethodes.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
                    NativMethodes.TOKEN_TYPE.TokenPrimary,
                    out token);

                // did DuplicateToken fail?
                if (false == bRetVal)
                {
                    int nErrorCode = Marshal.GetLastWin32Error();
                    // close existing handle
                    NativMethodes.CloseHandle(pExistingTokenHandle);

                    throw new Win32Exception("bRetVal");
                }
                else
                    return token;
            }
        }
        catch (Exception ex)
        {

            throw ex;
        }
        finally
        {
            // close handle(s)
            if (pExistingTokenHandle != IntPtr.Zero)
                NativMethodes.CloseHandle(pExistingTokenHandle);
        }
    }

and:

        IntPtr pDuplicateTokenHandle = IntPtr.Zero;
        try
        {
            pDuplicateTokenHandle = ImpersonateUserClass.ImpersonateUser(user.UserName, user.Domain, user.Password);
            string @path = Path.GetDirectoryName(strProcessFilename);

            var sec = new NativMethodes.SECURITY_ATTRIBUTES();
            var si = new NativMethodes.STARTUPINFO();
            var pi = new NativMethodes.PROCESS_INFORMATION();

            /*
                Click Start, Run. 
                Type gpedit.msc and click ok. 
                In the group policy editor: 
                Expand Windows Settings 
                Expand Security Settings 
                Expand Local Policies 
                Click on User Rights Assignment                  
             */

            if (NativMethodes.CreateProcessAsUser(pDuplicateTokenHandle, strProcessFilename,
                string.Format("{0} {1}", 0, strCommand), ref sec, ref sec, false,
                (uint)NativMethodes.CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero,
                @path, ref si, out pi))
            {
                int err = Marshal.GetLastWin32Error();
                if (err != 0)
                    throw new Exception("Failed CreateProcessAsUser error: " + new Win32Exception());
                try
                {
                    _process = Process.GetProcessById(pi.dwProcessId);
                    if (_process != null)
                    {
                        _process.WaitForExit();
                    }
                    else
                    {
                        NativMethodes.WaitForSingleObject(pDuplicateTokenHandle, NativMethodes.INFINITE);
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Not able to wait for the program", ex);
                }
            }
            else
                throw new Exception("Failed CreateProcessAsUser error: " + new Win32Exception());
        }
        finally
        {
            if (pDuplicateTokenHandle != IntPtr.Zero)
                NativMethodes.CloseHandle(pDuplicateTokenHandle);
        }

        return "";
answered on Stack Overflow Dec 11, 2014 by Venson • edited Mar 15, 2017 by Paul Roub

User contributions licensed under CC BY-SA 3.0