如何从ASP.NET应用程序启动/停止Windows服务 - 安全问题

16

这是我的Windows/.NET安全性堆栈:

  • 在Windows Server 2003上以LocalSystem身份运行的Windows服务。
  • 在同一台机器上运行的.NET 3.5网站,在“默认”生产服务器IIS设置下运行(所以可能是NETWORKSERVICE用户?)

在我的默认VS2008 DEV环境中,我有这个方法,从ASP.NET应用程序调用它,它工作正常:

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

当我在生产服务器上运行时,从ServiceController中得到以下错误:

来源:System.ServiceProcess -> System.ServiceProcess.ServiceController -> IntPtr GetServiceHandle(Int32) -> System.InvalidOperationException 消息: 无法在此计算机上打开eTimeSheetReminderService服务。

为什么会发生这种情况,如何解决?

编辑:

答案在下面,主要在评论中,但为了澄清:

  1. 该问题与安全有关,并且是由于NETWORKSERVICE帐户没有足够的权限来启动/停止服务而发生的
  2. 我创建了一个本地用户帐户,并将其添加到PowerUsers组(该组具有几乎管理员权限)
  3. 我不想让整个Web应用程序一直模拟该用户,所以我只在操作服务的方法中模拟。我通过使用以下资源来帮助我在代码中完成这个过程:

MS KB文章这个链接,只是为了更好地理解

注意:我不是通过web.config进行模拟,而是在代码中完成的。请参见上面的MS KB文章。


它是什么类型的异常?一个'System.InvalidOperationException'吗? - Phaedrus
@Phaedrus:嘿,是的,没错。我在编辑中加入了更多的错误信息。谢谢。 - andy
6个回答

14
为了让IIS有权限启动/停止特定的服务:
  • 下载并安装 Subinacl.exe。(请确保获取到最新版本!一些资源套件中分发的早期版本无法使用!
  • 执行类似于以下命令:subinacl /service {yourServiceName} /grant=IIS_WPG=F

这将授予内置的IIS_WPG组对该特定服务的完全服务控制权。(适用于IIS6 / Win2k3。)对于更新版本的IIS,结果可能会有所不同。


完美,对我很有帮助,而且我甚至不需要在我的web.config中添加模拟。干杯! - Nabster
这是我认为更好的解决方案。感谢Martin_ATS。 - amiir
使用此工具为用户授予权限,通过模拟成拥有启动和停止服务权限的用户来实现身份模拟。 - 130nk3r5

6
尝试将此添加到您的Web.Config文件中。
<identity impersonate="true"/>

IIS 中是否启用了匿名访问? - Phaedrus
是的,“启用匿名访问”已被选中。 - andy
禁用它,启用集成Windows身份验证。 - Phaedrus
啊哈!我们有所进展。好的,那个有效。然而,我需要开启匿名访问,并且我更愿意只给予需要它的代码部分特殊权限。有什么想法吗?干杯!! - andy
不,启用集成的Windows身份验证来运行公共站点可能不是最好的选择。这只是一种找出问题是否与安全相关的方法。您可能需要启用匿名访问并为IUSR_<yourcomputername>帐户授予适当的权限,以便它具有足够的访问您的服务的权限。 - Phaedrus
显示剩余2条评论

1
这是一个很好的问题,也让我感到困惑...所以这就是我解决这个问题的方法:
  • 步骤1:在本地计算机上创建一个具有最小权限的Windows用户帐户。
  • 步骤2:通过subinacl.exe授予此用户启动和停止服务的权限
  • 例如:subinacl.exe /service WindowsServiceName /GRANT=PCNAME\TestUser=STOE
  • 下载链接:http://www.microsoft.com/en-za/download/details.aspx?id=23510
  • 步骤3:使用模拟来模拟第1步创建的用户以启动和停止服务

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

LOGON32_LOGON_INTERACTIVE 对我不起作用,但幸好有这个答案 https://dev59.com/imnWa4cB1Zd3GeqP2JvO#28503968,我将其更改为LOGON32_LOGON_NETWORK后就可以正常工作了。 - ThePatelGuy

1

IIS 8更新(以及一些稍早版本)

用户组IIS_WPG已经不存在了,现在改为IIS_IUSRS

此外,启动停止服务不需要给予完全权限(F),授予启动、停止和暂停服务的权限(TOP)即可。因此命令应该是:

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

请注意,在运行此命令之前,您需要将命令提示符(最好是以管理员身份运行)指向C:\Windows\System32文件夹。

如果出现错误,请确保已将subinacl.exe文件从安装目录复制到C:\Windows\System32


0
如果您的Web应用程序具有数据库并且Windows服务可以访问它,则可以在DB中使用标志来重新启动该服务。在服务中,您可以读取此标志,并在不忙等情况下重新启动。仅当您可以修改服务代码时才会出现这种情况。 如果它是第三方服务,您可以创建自己的Windows服务并使用数据库配置来控制(重新启动)服务。这是安全的方式,可以为您提供更多的灵活性和安全性。

0

我有一种直觉,但我认为错误并不一定与安全有关。您在生产服务器上给服务起了相同的名称吗?


@cdonner:是的,我也怀疑是这个问题,但不知道该如何测试?是的,名称相同,在ServiceInstaller组件中定义。您有什么想法,如何得到一个确定的答案,是否涉及安全问题? 谢谢 - andy
你能否使用NET START/STOP命令行启动和停止它? - cdonner
兄弟,是的,在命令提示符中运行“NET START eTimeSheetReminderService”可以成功启动该服务。 - andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接