2

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.

LocEngineer
  • 2,847
  • 1
  • 16
  • 28
  • does [domain]\[username] have permissions to run on the machine where you're seeing the error? – Matthew Thurston Jul 20 '16 at 19:53
  • @MattThurston That user has full permission. To run the application, change registry entries and so forth. If I log on via RDP as that very same user I can do whatever the hell I like. It's not user permission per se, but obviously some kind of trust setting only taking effect when impersonating that user from asp.net / MVC / IIS / invoked second process. I think if I can self-determine the app pool identity Hangfire runs as, I should be able to fix this. – LocEngineer Jul 20 '16 at 20:24
  • @LocEngineer, how about to write Windows Service and use IPC (sockets, pipes and etc) to communicate with it. The functionality of that service will be just starting some exe file and return its ExitCode. – Artavazd Balayan Jul 21 '16 at 16:39

2 Answers2

5

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.

  • Yes, I am pretty certain it is caused by some trust setting. Your suggestions sound good. Alas, adjusting trust setting in web.config gives me an error `It is an error to use a section registered as allowDefinition='MachineToApplication' beyond application level.` when I try to publish. Already tried several workarounds mentioned on SO, nothing works so far. So, I am trying to simply set the trust setting on the exe itself with click-once. Will take a bit though as I managed to break my app and even my latest SourceTree restore did not fix the new problem. :-? Will get back a.s.a.p. – LocEngineer Jul 19 '16 at 11:51
  • Can you specify your .net and mvc version? Just to be on the same page – Alena Kastsiukavets Jul 19 '16 at 11:58
  • targeting Framework: 4.5, MVC 5, .exe needs to be started via Hangfire 1.6 BackgroundJob (i.e. by DefaultAppPool rather than the app-specific appPool; gues I should have included that info). – LocEngineer Jul 19 '16 at 12:01
  • So, you call Process.Start not from controller method, but from BackgroundJob? – Alena Kastsiukavets Jul 19 '16 at 13:35
  • I have a controller method called `DoCheckIn`. In one ActionResult (POST) in my controller I call `BackgroundJob.Enqueue(() => DoCheckIn(dict, rootpfad, projid) );` before returning the View, and let Hangfire call the method in the background. – LocEngineer Jul 19 '16 at 13:42
  • Hm, I do not know Hangfire insides, but as I see it has its own queue\thread pool to run threads. Have you tried to guarantee full trust to Hangfire lib? Anyway, I suggest to address this issue directly on GitHub – Alena Kastsiukavets Jul 19 '16 at 14:18
  • Btw, [this issue](https://github.com/HangfireIO/Hangfire/issues/390) says that legacyCasModel="true", so [the last section](https://msdn.microsoft.com/en-us/library/wyts434y.aspx) can give some hint – Alena Kastsiukavets Jul 19 '16 at 14:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/117750/discussion-between-alena-kastsiukavets-and-locengineer). – Alena Kastsiukavets Jul 19 '16 at 19:59
  • [Issue](http://stackoverflow.com/questions/15000265/which-permissions-are-required-to-enable-process-start-via-asp-net-application) similar to yours, see comment: _In my case I assumed that this was in place since my app pool identity was part of the administrtors group, but the file system permissions were set by Plesk which excluded the files involved_ – Alena Kastsiukavets Jul 20 '16 at 08:39
  • I think that gives me enough information to work on. I will continue to post my progress until the issue is fully solved. Will take a bit though since I will be off until next week. Thanks a million! – LocEngineer Jul 20 '16 at 12:32
  • Here my final update how I finally got it working: Even with setting `FileIOPermissions` attribute for the method, neither setting user name for `ProcessStartInfo` (conhost crash) nor `Impersonation` worked (access denied for reading registry entry). In the end, I had to create a WCF service with a dedicated AppPool running with the desired Identity as a helper that starts the actual target exe. At long last it works. Thanks a mil! – LocEngineer Aug 04 '16 at 16:04
  • @LocEngineer I have a function which requires elevation. The application is launched in a non-admin mode. The functions are in dll file. So, the functions even expect parameters and return values. So, how can I achieve this ? Can wcf be any useful ? If so can you help me with some resource links ? – Susarla Nikhilesh Jan 10 '19 at 06:08
  • @SusarlaNikhilesh : Please post a separate question for this with the relevant code parts so we know what exactly you wish to achieve and where exactly you are stuck. The comment section is unfit for this. – LocEngineer Jan 10 '19 at 09:30
0

You can try trust level in web.config of your application

<system.web>
  <securityPolicy>
    <trustLevel name="Full" policyFile="internal"/>
  </securityPolicy>
</system.web>
Y.B.
  • 3,526
  • 14
  • 24
Manish Kumar
  • 509
  • 5
  • 15