How to Start/Stop a Windows Service from an ASP.NET app - Security issues
Asked Answered
E

6

17

Here's my Windows/.NET security stack:

  • A Windows Service running as LocalSystem on a Windows Server 2003 box.
  • A .NET 3.5 Website running on the same box, under "default" production server IIS settings (so probably as NETWORKSERVICE user?)

On my default VS2008 DEV environment I have this one method, which gets called from the ASP.NET app, which works fine:

private static void StopStartReminderService() {

    ServiceController svcController = new ServiceController("eTimeSheetReminderService");

    if (svcController != null) {
        try {
            svcController.Stop();
            svcController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(10));
            svcController.Start();
        } catch (Exception ex) {
            General.ErrorHandling.LogError(ex);
        }
    }
}

When I run this on the production server, I get the following error from the ServiceController:

Source: System.ServiceProcess -> System.ServiceProcess.ServiceController -> IntPtr GetServiceHandle(Int32) -> System.InvalidOperationException Message: Cannot open eTimeSheetReminderService service on computer '.'.

Why is this happening, and how do I fix it?

EDIT:

The answer is below, mostly in comments, but to clarify:

  1. The issue was Security related, and occurred because the NETWORKSERVICE account did not have sufficient rights to Start/Stop a service
  2. I created a Local User Account, and added it to the PowerUsers Group (this group has almost admin rights)
  3. I don't want my whole Web App to impersonate that user all the time, so I impersonate only in the method where I manipulate the service. I do this by using the following resources to help me do it in code:

MS KB article and this, just to get a better understanding

NOTE: I don't impersonate via the web.config, I do it in code. See the MS KB Article above.

Emory answered 4/5, 2009 at 1:36 Comment(2)
What type of exception was it? A 'System.InvalidOperationException'?Crenulate
@Phaedrus: hey, yeah, yeah it was. I've put some more error info in my edit. cheersEmory
C
6

Try adding this to your Web.Config.

<identity impersonate="true"/>
Crenulate answered 4/5, 2009 at 3:4 Comment(7)
Is anonymous access enabled in IIS?Crenulate
yep, "Enable anonymous access" is tickedEmory
Disable it, enable Integrated Windows Authentication.Crenulate
cool, I'll try it out. How will that affect access to the site? it is a public site. Also, can impersonating be done just for that one method? Anyway, I'll try it out to see if it works, thanks, much appreciated.Emory
ah ha! we're getting somewhere. ok, that worked. However, I need Anonymous access turned on, and I'd rather only give special privileges to sections of code that need it. Any ideas? cheers!!Emory
No, running a public site with Integrated Windows Authentication enabled probably isn't the best idea. It's just a way to find out if your problem is in fact related to security. Your probably going to want to enable anonymous access and give the appropriate permissions to the IUSR_<yourcomputername> account so that it has sufficient rights to access your service.Crenulate
cool, thanks Phaedrus. yes, I figured you only wanted to eliminate possibilities, awesome. Ok, this is the bit that gets me. So I have to find the Account that my ASP.NET app is running under, right? and then how do I give it rights to access the service, what rights would that be? Is there a way to give it rights to that particular service only, or do I just have to give complete control to the IUSR_etc Account?Emory
B
14

To give IIS permission to start/stop a particular service:

  • Download and install Subinacl.exe. (Be sure to get the latest version! Earlier versions distributed in some resource kits don't work!)
  • Issue a command similar to: subinacl /service {yourServiceName} /grant=IIS_WPG=F

This grants full service control rights for that particular service to the built-in IIS_WPG group. (This works for IIS6 / Win2k3.) YMMV for newer versions of IIS.)

Brycebryn answered 3/5, 2012 at 1:14 Comment(3)
Perfect, did the trick for me and I didn't even have to add impersonation to my web.config. Cheers!Stannary
This is the better solution in my opinion. Thanks Martin_ATS.Coriecorilla
Use this tool to give a user rights, whereby you use impersonation to impersonate the user that has rights to start and stop the service.Listlessness
C
6

Try adding this to your Web.Config.

<identity impersonate="true"/>
Crenulate answered 4/5, 2009 at 3:4 Comment(7)
Is anonymous access enabled in IIS?Crenulate
yep, "Enable anonymous access" is tickedEmory
Disable it, enable Integrated Windows Authentication.Crenulate
cool, I'll try it out. How will that affect access to the site? it is a public site. Also, can impersonating be done just for that one method? Anyway, I'll try it out to see if it works, thanks, much appreciated.Emory
ah ha! we're getting somewhere. ok, that worked. However, I need Anonymous access turned on, and I'd rather only give special privileges to sections of code that need it. Any ideas? cheers!!Emory
No, running a public site with Integrated Windows Authentication enabled probably isn't the best idea. It's just a way to find out if your problem is in fact related to security. Your probably going to want to enable anonymous access and give the appropriate permissions to the IUSR_<yourcomputername> account so that it has sufficient rights to access your service.Crenulate
cool, thanks Phaedrus. yes, I figured you only wanted to eliminate possibilities, awesome. Ok, this is the bit that gets me. So I have to find the Account that my ASP.NET app is running under, right? and then how do I give it rights to access the service, what rights would that be? Is there a way to give it rights to that particular service only, or do I just have to give complete control to the IUSR_etc Account?Emory
L
1

This was a good question that intrigued me as well...

So here is what I did to solve this problem:

  • Step 1: Create a Windows user account on the local machine with minimal rights.
  • Step 2: Give this user rights to start and stop the service via subinacl.exe
  • i.e. subinacl.exe /service WindowsServiceName /GRANT=PCNAME\TestUser=STOE
  • Dowload from : http://www.microsoft.com/en-za/download/details.aspx?id=23510
  • Step 3: Use Impersonation to impersonate the use created in Step 1 to start and stop the Service

    public const int LOGON32_PROVIDER_DEFAULT = 0;
    
    WindowsImpersonationContext _impersonationContext;
    
    [DllImport("advapi32.dll")]
    // ReSharper disable once MemberCanBePrivate.Global
    public static extern int LogonUserA(String lpszUserName,
        String lpszDomain,
        String lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    // ReSharper disable once MemberCanBePrivate.Global
    public static extern int DuplicateToken(IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);
    
    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    // ReSharper disable once MemberCanBePrivate.Global
    public static extern bool RevertToSelf();
    
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    // ReSharper disable once MemberCanBePrivate.Global
    public static extern bool CloseHandle(IntPtr handle);
    
    private bool _impersonate;
    
    public bool ImpersonateValidUser(String userName, String domain, String password)
    {
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;
    
        if (RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    var tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    _impersonationContext = tempWindowsIdentity.Impersonate();
                    if (_impersonationContext != null)
                    {
                        CloseHandle(token);
                        CloseHandle(tokenDuplicate);
                        _impersonate = true;
                        return true;
                    }
                }
            }
        }
        if (token != IntPtr.Zero)
            CloseHandle(token);
        if (tokenDuplicate != IntPtr.Zero)
            CloseHandle(tokenDuplicate);
        _impersonate = false;
        return false;
    }
    
    #region Implementation of IDisposable
    
    
    
    
    #endregion
    
    #region Implementation of IDisposable
    
    private void Dispose(bool dispose)
    {
        if (dispose)
        {
            if (_impersonate)
                _impersonationContext.Undo();
            _impersonationContext.Dispose();
        }
    }
    
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion
    
    public static void StartStopService(bool startService, string serviceName)
    {
        using (var impersonateClass = new Impersonation())
        {
            impersonateClass.ImpersonateValidUser(Settings.Default.LocalUsername, Settings.Default.Domain, Settings.Default.Password);
            using (var sc = new ServiceController(serviceName))
            {
                if (startService)
                    sc.Start();
                else if (sc.CanStop)
                    sc.Stop();
            }
    
        }
    }
    
Listlessness answered 13/10, 2014 at 10:5 Comment(1)
LOGON32_LOGON_INTERACTIVE didn't work for me but thanks to this answer https://mcmap.net/q/745347/-start-stop-service-from-an-asp-net-page I changed it to LOGON32_LOGON_NETWORK and worked fine.Schilit
D
1

Update for IIS 8 (and maybe some slightly earlier versions)

The usergroup IIS_WPG does not exist anymore. It has changed to IIS_IUSRS.

Also, to start stop a service it is not neccesary to give full permissions (F). Permissions to start, stop and pause a service (TOP) should be enough. As such the command should be:

subinacl /service {yourServiceName} /grant=IIS_IUSRS=TOP

Note that you need to point the command prompt (preferably elevated to run as administrator) to C:\Windows\System32 Folder before running this command.

Also make sure that you have copied the subinacl.exe file to C:\Windows\System32 from the installation directory if there is an error.

Disbar answered 14/7, 2017 at 4:9 Comment(0)
S
0

Just a hunch, but it does not appear to me the error is necessarily related to security. Did you give the service the same name on the production server?

Schnabel answered 4/5, 2009 at 1:55 Comment(3)
@cdonner: yeah, I suspected that too, but didn't know how I could test it? Yes, the name is the same, its defined in the code, in the ServiceInstaller component. Any ideas on how I could get a definite answer on whether it even is security? cheersEmory
can you start and stop it from the command line, using NET START/STOP?Schnabel
hey man, yeah, running "NET START eTimeSheetReminderService" in the command prompt starts the service successfullyEmory
F
0

If your web application has the database and windows service can access it, you can just use the flag in the DB to restart the service. In the service, you can read this flag and restart if not busy etc. Only in case if you can modify the code of the service. If it's third party service you can create your own windows service and use database config to control (restart) services. It's the safe way and gives you much more flexibility and security.

Falcon answered 7/10, 2016 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.