How to Start/Stop a Windows Service from an ASP.NET app - Security issues
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.
Comments
-
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:
- The issue was Security related, and occurred because the NETWORKSERVICE account did not have sufficient rights to Start/Stop a service
- I created a Local User Account, and added it to the PowerUsers Group (this group has almost admin rights)
- 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.