Domanda

Ho tentato di creare un nuovo processo nel contesto di un utente specifico utilizzando il file CreateProcessAsUser funzione dell'API di Windows, ma sembra che si stia verificando un problema di sicurezza piuttosto fastidioso...

Prima di spiegare ulteriormente, ecco il codice che sto attualmente utilizzando per avviare il nuovo processo (un processo della console, PowerShell per essere precisi, anche se non dovrebbe avere importanza).

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

Per il bene del problema qui, ignora il codice che riguarda le variabili d'ambiente (ho testato quella sezione in modo indipendente e sembra funzionare.)

Ora, l'errore che ottengo è il seguente (lanciato sulla riga successiva alla chiamata a CreateProcessAsUSer):

"Un privilegio richiesto non è detenuto dal client" (codice errore 1314)

(Il messaggio di errore è stato rilevato rimuovendo il parametro message dal costruttore Win32Exception.Certo, il mio codice di gestione degli errori qui potrebbe non essere il migliore, ma è una questione alquanto irrilevante.Puoi comunque commentarlo se lo desideri.) Sono davvero abbastanza confuso riguardo alla causa di questo vago errore in questa situazione.La documentazione MSDN e vari thread del forum mi hanno dato solo pochi consigli e, soprattutto dato che le cause di tali errori sembrano essere molto diverse, non ho idea di quale sezione di codice devo modificare.Forse è semplicemente un singolo parametro che devo modificare, ma per quanto ne so potrei aver effettuato chiamate WinAPI sbagliate/non sufficienti.Ciò che mi confonde molto è che la versione precedente del codice utilizzava il metodo plain CreateProcess La funzione (equivalente ad eccezione del parametro token utente) ha funzionato perfettamente.A quanto ho capito, è solo necessario chiamare la funzione utente Logon per ricevere l'handle del token appropriato e quindi duplicarlo in modo che possa essere passato a CreateProcessAsUser.

Eventuali suggerimenti per modifiche al codice e spiegazioni sarebbero molto graditi.

Appunti

Mi riferisco principalmente ai documenti MSDN (oltre a PINvoke.net per le dichiarazioni di funzione/strut/enum C#).Le seguenti pagine in particolare sembrano contenere molte informazioni nelle sezioni Osservazioni, alcune delle quali potrebbero essere importanti e mi sfuggono:

Modificare

Ho appena provato il suggerimento di Mitch, ma sfortunatamente il vecchio errore è stato appena sostituito da uno nuovo:"Il sistema non trova il file specificato." (Codice di errore 2)

La chiamata precedente a CreateProcessAsUser è stato sostituito con il seguente:

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

Tieni presente che questo codice non utilizza più il token duplicato ma piuttosto l'originale, come sembrano suggerire i documenti MSDN.

Ed ecco un altro tentativo di utilizzo CreateProcessWithLogonW.L'errore questa volta è "Errore di accesso:nome utente sconosciuto o password errata" (codice errore 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);

Ho anche provato a specificare il nome utente in formato UPN ("Alex@Alex-PC") e a passare il dominio in modo indipendente come secondo argomento, ma senza alcun risultato (errore identico).

È stato utile?

Soluzione

Ahh...sembra che io sia stato colto di sorpresa da uno dei più grandi trucchi nella programmazione di interoperabilità WinAPI.Inoltre, pubblicare il codice per le mie dichiarazioni di funzione sarebbe stata un'idea saggia in questo caso.

Ad ogni modo, tutto quello che dovevo fare era aggiungere un argomento all'attributo DllImport della funzione che specifica CharSet = CharSet.Unicode.Questo ha funzionato per entrambi CreateProcessWithLogonW E CreateProcessWithTokenW funzioni.Immagino che alla fine mi abbia colpito il fatto che il suffisso W dei nomi delle funzioni si riferisse a Unicode e che dovevo specificarlo esplicitamente in C#!Ecco i corretto dichiarazioni di funzioni nel caso qualcuno sia interessato:

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

Altri suggerimenti

qui :

  

In genere, il processo che chiama il   la funzione CreateProcessAsUser deve avere   il SE_ASSIGNPRIMARYTOKEN_NAME e   privilegi SE_INCREASE_QUOTA_NAME. Se   questa funzione non riesce con   ERROR_PRIVILEGE_NOT_HELD (1314), l'uso   la funzione CreateProcessWithLogonW   anziché. CreateProcessWithLogonW   non richiede privilegi speciali, ma   l'account utente specificato deve essere   permesso di accedere in modo interattivo.   In generale, è meglio usare   CreateProcessWithLogonW per creare un   processo con credenziali alternative.

Si veda questo post del blog Come chiamare CreateProcessWithLogonW & CreateProcessAsUser in .NET

Peppers Jonathan a condizione che questa grande pezzo di codice che fissa i miei problemi

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top