Pregunta

He estado intentando crear un nuevo proceso bajo el contexto de un usuario específico usando el CreateProcessAsUser función de la API de Windows, pero parece que se está topando con un problema de seguridad bastante desagradable...

Antes de seguir explicando, aquí está el código que estoy usando actualmente para iniciar el nuevo proceso (un proceso de consola, PowerShell para ser específico, aunque no debería importar).

    private void StartProcess()
    {
        bool retValue;

        // Create startup info for new console process.
        var startupInfo = new STARTUPINFO();
        startupInfo.cb = Marshal.SizeOf(startupInfo);
        startupInfo.dwFlags = StartFlags.STARTF_USESHOWWINDOW;
        startupInfo.wShowWindow = _consoleVisible ? WindowShowStyle.Show : WindowShowStyle.Hide;
        startupInfo.lpTitle = this.ConsoleTitle ?? "Console";

        var procAttrs = new SECURITY_ATTRIBUTES();
        var threadAttrs = new SECURITY_ATTRIBUTES();
        procAttrs.nLength = Marshal.SizeOf(procAttrs);
        threadAttrs.nLength = Marshal.SizeOf(threadAttrs);

        // Log on user temporarily in order to start console process in its security context.
        var hUserToken = IntPtr.Zero;
        var hUserTokenDuplicate = IntPtr.Zero;
        var pEnvironmentBlock = IntPtr.Zero;
        var pNewEnvironmentBlock = IntPtr.Zero;

        if (!WinApi.LogonUser("UserName", null, "Password",
            LogonType.Interactive, LogonProvider.Default, out hUserToken))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error logging on user.");

        var duplicateTokenAttrs = new SECURITY_ATTRIBUTES();
        duplicateTokenAttrs.nLength = Marshal.SizeOf(duplicateTokenAttrs);
        if (!WinApi.DuplicateTokenEx(hUserToken, 0, ref duplicateTokenAttrs,
            SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary,
            out hUserTokenDuplicate))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error duplicating user token.");

        try
        {
            // Get block of environment vars for logged on user.
            if (!WinApi.CreateEnvironmentBlock(out pEnvironmentBlock, hUserToken, false))
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Error getting block of environment variables for user.");

            // Read block as array of strings, one per variable.
            var envVars = ReadEnvironmentVariables(pEnvironmentBlock);

            // Append custom environment variables to list.
            foreach (var var in this.EnvironmentVariables)
                envVars.Add(var.Key + "=" + var.Value);

            // Recreate environment block from array of variables.
            var newEnvironmentBlock = string.Join("\0", envVars.ToArray()) + "\0";
            pNewEnvironmentBlock = Marshal.StringToHGlobalUni(newEnvironmentBlock);

            // Start new console process.
            retValue = WinApi.CreateProcessAsUser(hUserTokenDuplicate, null, this.CommandLine,
                ref procAttrs, ref threadAttrs, false, CreationFlags.CREATE_NEW_CONSOLE |
                CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
                pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
            if (!retValue) throw new Win32Exception(Marshal.GetLastWin32Error(),
                "Unable to create new console process.");
        }
        catch
        {
            // Catch any exception thrown here so as to prevent any malicious program operating
            // within the security context of the logged in user.

            // Clean up.
            if (hUserToken != IntPtr.Zero)
            {
                WinApi.CloseHandle(hUserToken);
                hUserToken = IntPtr.Zero;
            }

            if (hUserTokenDuplicate != IntPtr.Zero)
            {
                WinApi.CloseHandle(hUserTokenDuplicate);
                hUserTokenDuplicate = IntPtr.Zero;
            }

            if (pEnvironmentBlock != IntPtr.Zero)
            {
                WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
                pEnvironmentBlock = IntPtr.Zero;
            }

            if (pNewEnvironmentBlock != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pNewEnvironmentBlock);
                pNewEnvironmentBlock = IntPtr.Zero;
            }

            throw;
        }
        finally
        {
            // Clean up.
            if (hUserToken != IntPtr.Zero)
                WinApi.CloseHandle(hUserToken);

            if (hUserTokenDuplicate != IntPtr.Zero)
                WinApi.CloseHandle(hUserTokenDuplicate);

            if (pEnvironmentBlock != IntPtr.Zero)
                WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);

            if (pNewEnvironmentBlock != IntPtr.Zero)
                Marshal.FreeHGlobal(pNewEnvironmentBlock);
        }

        _process = Process.GetProcessById(_processInfo.dwProcessId);
    }

Por el bien del problema aquí, ignore el código que trata con las variables de entorno (he probado esa sección de forma independiente y parece funcionar).

Ahora, el error que recibo es el siguiente (arrojado en la línea que sigue a la llamada a CreateProcessAsUSer):

"El cliente no posee un privilegio requerido" (código de error 1314)

(El mensaje de error se descubrió eliminando el parámetro de mensaje del constructor Win32Exception.Es cierto que mi código de manejo de errores aquí puede no ser el mejor, pero eso es un asunto algo irrelevante.Sin embargo, puedes comentarlo si lo deseas). Realmente estoy bastante confundido en cuanto a la causa de este vago error en esta situación.La documentación de MSDN y varios hilos del foro solo me han brindado muchos consejos y, especialmente dado que las causas de tales errores parecen ser muy variadas, no tengo idea de qué sección de código necesito modificar.Quizás sea simplemente un parámetro único que necesito cambiar, pero, por lo que sé, podría estar haciendo llamadas WinAPI incorrectas o insuficientes.Lo que me confunde mucho es que la versión anterior del código que usa el formato simple CreateProcess La función (equivalente excepto el parámetro token de usuario) funcionó perfectamente bien.Según tengo entendido, solo es necesario llamar a la función de usuario Logon para recibir el identificador de token apropiado y luego duplicarlo para que pueda pasarse a CreateProcessAsUser.

Cualquier sugerencia de modificación del código, así como explicaciones, serán bienvenidas.

Notas

Me he estado refiriendo principalmente a los documentos de MSDN (así como a PInvoke.net para las declaraciones de función/strut/enum de C#).Las siguientes páginas en particular parecen tener mucha información en las secciones de Comentarios, algunas de las cuales pueden ser importantes y se me escapan:

Editar

Acabo de probar la sugerencia de Mitch, pero desafortunadamente el error anterior acaba de ser reemplazado por uno nuevo:"El sistema no puede encontrar el archivo especificado." (Código de error 2)

La convocatoria anterior a CreateProcessAsUser fue reemplazado por lo siguiente:

retValue = WinApi.CreateProcessWithTokenW(hUserToken, LogonFlags.WithProfile, null,
    this.CommandLine, CreationFlags.CREATE_NEW_CONSOLE |
    CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
    pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);

Tenga en cuenta que este código ya no utiliza el token duplicado sino el original, como parecen sugerir los documentos de MSDN.

Y aquí hay otro intento usando CreateProcessWithLogonW.El error esta vez es "Error de inicio de sesión:nombre de usuario desconocido o contraseña incorrecta" (código de error 1326)

retValue = WinApi.CreateProcessWithLogonW("Alex", null, "password",
    LogonFlags.WithProfile, null, this.CommandLine,
    CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED |
    CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock,
    null, ref startupInfo, out _processInfo);

También intenté especificar el nombre de usuario en formato UPN ("Alex@Alex-PC") y pasar el dominio de forma independiente como segundo argumento, todo fue en vano (error idéntico).

¿Fue útil?

Solución

Ah...Parece más probable que me haya sorprendido uno de los mayores problemas en la programación de interoperabilidad de WinAPI.Además, publicar el código para mis declaraciones de funciones habría sido una buena idea en este caso.

De todos modos, todo lo que necesitaba hacer era agregar un argumento al atributo DllImport de la función que especifica CharSet = CharSet.Unicode.Esto funcionó tanto para el CreateProcessWithLogonW y CreateProcessWithTokenW funciones.¡Supongo que finalmente me di cuenta de que el sufijo W de los nombres de las funciones se refería a Unicode y que necesitaba especificar esto explícitamente en C#!Aquí están los correcto declaraciones de funciones en caso de que alguien esté interesado:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessWithLogonW(string principal, string authority,
    string password, LogonFlags logonFlags, string appName, string cmdLine,
    CreationFlags creationFlags, IntPtr environmentBlock, string currentDirectory,
    ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInfo);

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessWithTokenW(IntPtr hToken, LogonFlags dwLogonFlags,
    string lpApplicationName, string lpCommandLine, CreationFlags dwCreationFlags,
    IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);

Otros consejos

aquí :

  

Normalmente, el proceso que llama a la   CreateProcessAsUser función debe tener   la SE_ASSIGNPRIMARYTOKEN_NAME y   SE_INCREASE_QUOTA_NAME privilegios. Si   esta función falla con   ERROR_PRIVILEGE_NOT_HELD (1314), el uso   la función CreateProcessWithLogonW   en lugar. CreateProcessWithLogonW   no requiere privilegios especiales, pero   la cuenta de usuario especificado debe ser   permitido para iniciar la sesión de forma interactiva.   En general, lo mejor es utilizar   CreateProcessWithLogonW para crear una   proceso con credenciales alternativas.

Vea esta entrada del blog Cómo llamar CreateProcessWithLogonW y CreateProcessAsUser en .NET

Jonathan Pimientos siempre que esta gran pieza de código que fija mis problemas

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb?ppud=4

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top