Search code examples
c#asp.net-mvcprocessimpersonation

MVC: start process as different user - not working


I need to run an executable on the server from an MVC controller. Problem: the executable sits within Program Files folder and will also read a value from registry. I have granted execution rights on the respective folder to my application pool. So here's my problem:

Running the exe just with Process.Start(exe) will start the executable which in turn then exits with an error because it cannot read the registry value (no access).

Assigning a local admin user to ProcessStartInfo fails:

var exe = @"C:\Program Files (x86)\[path to exe]";

var secString = new SecureString();
secString.AppendChar('character');
//...
secString.MakeReadOnly();

var procInfo = new ProcessStartInfo(exe, settingsPath)
{
    UseShellExecute = false,
    UserName = "[username]",
    Domain = "[domain]",
    Password = secString,
    RedirectStandardError = true,
    RedirectStandardOutput = true,
    RedirectStandardInput = true,
    Verb = "runas"
};
var proc = Process.Start(procInfo);
proc.WaitForExit();

This will cause a crash of conhost and the executable.

Using impersonation like this:

var impers = new ImpersonationService();
impers.PerformImpersonatedTask("[user]", "[domain]", "[password]",
ImpersonationService.LOGON32_LOGON_INTERACTIVE, ImpersonationService.LOGON32_PROVIDER_DEFAULT, new Action(RunClient));

...with the method RunClient() simply using Process.Start(exe) will do absolutely nothing! The method is run but the process is not being started. I know that the method is run because I added this line to it:

_logger.Debug("Impersonated: {0}", Environment.UserName);

Which correctly gives me the desired user name the process shall use. That user has local Admin privileges, so there should not be an issue there.

I have even tried starting a different executable from my Controller and have that one use impersonation (both variants) to start the target executable - same outcome.

So right now I'm at a dead end. Can anyone please tell me what I'm doing wrong and what I have to do to make it work?

P.S: running the target executable directly on the server when logged in as the local admin user works perfectly fine, so no prob with the exe itself.

Edit:

It seems one part of my description was incorrect: with impersonation and RunClient method I actually did not use Process.Start(exe) but this:

var procInfo = new ProcessStartInfo(exe, settingsPath)
{
    UseShellExecute = false,
};
_logger.Debug("Impersonated: {0}", Environment.UserName);
var proc = Process.Start(procInfo);

Out of desperation I have now circumvented procInfo(don't actually need it) and really called

var proc = Process.Start(exe, argument);

And now the .exe starts! It seems using ProcessStartInfo overrides the impersonation for the process??

Still not OK though, as now I get an "Access denied" error. Despite being local admin. This is just weird.

Edit 2: This is how my latest attempt went:

  • Switched back to calling a helper .exe, passing the same arguments later used for the actual target exe in Program Files
  • added a manifest to that helper exe with level="requireAdministrator"
  • Added Impersonation to my helper exe according to https://msdn.microsoft.com/en-us/library/w070t6ka(v=vs.110).aspx with [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")] added before the method starting the target process.
  • Started the process by providing ProcessStartInfo with all the jazz

Resulting code:

try
{
    var secString = new SecureString();
    //...
    secString.MakeReadOnly();
    var procInfo = new ProcessStartInfo()
    {
        FileName = Path.GetFileName(exe),
        UserName = "[UserName]",
        Domain = "[domain]",
        Password = secString,
        UseShellExecute = false,
        RedirectStandardError = true,
        RedirectStandardOutput = true,
        RedirectStandardInput = true,
        Arguments = settingsPath,
        WorkingDirectory = @"C:\Program Files (x86)\[rest]"
    };
    var proc = Process.Start(procInfo);
    proc.WaitForExit();
    if (proc.ExitCode != 0)
    {
        using (var sw = new StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "error.log"), true))
        {
            sw.WriteLine("Error running process:\r\n{0}", proc.ExitCode.ToString());
        }
    }
}
catch (Exception ex)
{
    using (var sw = new StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "error.log"), true))
    {
        sw.WriteLine("Error running process:\r\n{0}\r\nRunning as: {1}", ex.ToString(), WindowsIdentity.GetCurrent().Name);
    }
}

Resulting output to error.log:

Helper running! [passed argument] Error running process: System.ComponentModel.Win32Exception (0x80004005): Access denied at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo) at System.Diagnostics.Process.Start() at System.Diagnostics.Process.Start(ProcessStartInfo startInfo) at RunClient.ImpersonationDemo.RunClient(String settingsPath) Running as: [correct domain user in Admin group]

So I can start the helper exe but that cannot start the real exe in Program Files due to Acess denied despite running under a local Admin account and all files access locally, not on network drives.

The logic of this eludes me.

Edit 3

Update: I have added a manifest to the target .exe also,

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

This means I now:

  • Call a helper exe from the controller: works
  • The helper .exe has a manifest to run with elevated rights (Admin)
  • The helper .exe uses impersonation to assume the identity of a local admin to start a process
  • Said process is started using a ProcessStartInfo in which username, domain, and password are additionally set to the same local admin user
  • The helper exe then tries to run the target exe using Process.Start(Startinfo) with the local admin user set, while still impersonating that user's windows identity

And still the error log spouts "Access denied" while correctly returning WindowsIdentity.GetCurrent().Name as that of the local admin.

And now, the greatest of all happened: I created a new local user on that server, added him to local admin group and used that user for impersonation just in case there is a problem with domain users. Guess what? Now I get an error Access denied to ...\error.log - written to the effing error log.

Really?

Edit 4

I think I'll try TopShelf to convert this shebang to a service. Hope to get this done over the weekend.


Solution

  • According to this article your mvc controller thread should have full-trust permission to run the process:

    This class contains a link demand at the class level that applies to all members. A SecurityException is thrown when the immediate caller does not have full-trust permission. For details about security demands, see Link Demands.

    Seems you problem is not the user but full-trust. I do not know which version of MVC you use but you can read the articles Trust Levels and Code Access to find out the best way to configure your application. Seems you can grant full-trust permission only to specific .exe file or grant full-trust permission to application pool user (do not forget about folder permissions).

    But the best approach is to write some windows service and run it instead of running some .exe file directly.