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

33,312

Solution 1

Try adding this to your Web.Config.

<identity impersonate="true"/>

Solution 2

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

Solution 3

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

Solution 4

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.

Share:
33,312
andy
Author by

andy

dad, photographer, software engineer. thatandyrose.com

Updated on July 09, 2022

Comments

  • andy
    andy almost 2 years

    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.