5

我有一个启用了 Windows 身份验证的网站。从网站中的一个页面,用户可以启动一个对数据库做一些事情的服务。

启动服务对我来说很好,因为我是服务器上的本地管理员。但我只是让一个用户测试它,他们无法启动服务。

我的问题是:


有谁知道使用与他们当前登录的帐户不同的 Windows 帐户按名称在指定计算机上获取服务列表的方法?


我真的不想将需要启动服务的所有用户添加到 Windows 组中,并将它们全部设置为我的 IIS 服务器上的本地管理员.....

这是我得到的一些代码:

public static ServiceControllerStatus FindService()
        {
            ServiceControllerStatus status = ServiceControllerStatus.Stopped;

            try
            {
                string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
                ServiceController[] services = ServiceController.GetServices(machineName);
                string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower();

                foreach (ServiceController service in services)
                {
                    if (service.ServiceName.ToLower() == serviceName)
                    {
                        status = service.Status;
                        break;
                    }
                }
            }
            catch(Exception ex)
            {
                status = ServiceControllerStatus.Stopped;
                SaveError(ex, "Utilities - FindService()");
            }

            return status;
        }

我的异常来自 try 块中的第二行。这是错误:

System.InvalidOperationException:无法在计算机“server.domain.com”上打开服务控制管理器。此操作可能需要其他权限。---> System.ComponentModel.Win32Exception:访问被拒绝---内部异常堆栈跟踪结束---在 System.ServiceProcess.ServiceController.GetDataBaseHandleWithAccess(String machineName, Int32 serviceControlManaqerAccess) 在 System.ServiceProcess.ServiceController.GetServicesOfType(String machineName, Int32 serviceType) 在 TelemarketingWebSite.Utilities.StartService()

感谢您的帮助/信息

4

3 回答 3

6

注意:这并不涉及将服务枚举为不同的用户,但考虑到您正在做的更广泛的描述,我认为这是一个很好的答案。

如果您直接访问感兴趣的服务,我认为您可以简化很多,并可能避免部分安全问题。而不是调用 GetServices,试试这个:

string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
string serviceName = ConfigurationManager.AppSettings["ServiceName"];
ServiceController service = new ServiceController( serviceName, machineName );
return service.Status;

这直接连接到感兴趣的服务并绕过枚举/搜索步骤。因此,它不需要调用者拥有SC_MANAGER_ENUMERATE_SERVICE服务控制管理器 (SCM) 的权限,而远程用户默认没有这些权限。它仍然需要SC_MANAGER_CONNECT,但根据 MSDN,应该授予远程经过身份验证的用户。

找到感兴趣的服务后,您仍然需要能够停止和启动它,而您的远程用户可能无权执行此操作。但是,可以修改单个服务的安全描述符 (DACL),这样您就可以授予远程用户停止和启动服务的权限,而无需他们是本地管理员。这是通过SetNamedSecurityInfo API 函数完成的。您需要授予的访问权限是SERVICE_STARTSERVICE_STOP。根据这些用户究竟属于哪些组,您可能还需要授予他们GENERIC_READ. 所有这些权利都在 MSDN 中进行了描述

下面是一些将执行此设置的 C++ 代码,假设感兴趣的用户位于“远程服务控制器”组(您将创建该组)中并且服务名称为“my-service-name”。请注意,如果您想授予对知名组的访问权限,例如用户(不一定是一个好主意)而不是您创建的组,您需要更改TRUSTEE_IS_GROUPTRUSTEE_IS_WELL_KNOWN_GROUP.

该代码没有您想要添加的错误检查。所有三个可能失败的函数(Get/SetNamedSecurityInfo 和 SetEntriesInAcl)都返回 0 表示成功。

另一个注意事项:您也可以使用SC 工具设置服务的安全描述符,该工具可以在 %WINDIR%\System32 下找到,但这不涉及任何编程。

#include "windows.h"
#include "accctrl.h"
#include "aclapi.h"

int main()
{
    char serviceName[] = "my-service-name";
    char userGroup[] = "Remote Service Controllers";

    // retrieve the security info
    PACL pDacl = NULL;
    PSECURITY_DESCRIPTOR pDescriptor = NULL;
    GetNamedSecurityInfo( serviceName, SE_SERVICE,
        DACL_SECURITY_INFORMATION, NULL, NULL,
        &pDacl, NULL, &pDescriptor );

    // add an entry to allow the users to start and stop the service
    EXPLICIT_ACCESS access;
    ZeroMemory( &access, sizeof(access) );
    access.grfAccessMode = GRANT_ACCESS;
    access.grfAccessPermissions = SERVICE_START | SERVICE_STOP;
    access.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    access.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    access.Trustee.ptstrName = userGroup;
    PACL pNewDacl;
    SetEntriesInAcl( 1, &access, pDacl, &pNewDacl );

    // write the changes back to the service
    SetNamedSecurityInfo( serviceName, SE_SERVICE,
        DACL_SECURITY_INFORMATION, NULL, NULL,
        pNewDacl, NULL );

    LocalFree( pNewDacl );
    LocalFree( pDescriptor );
}

这也可以在 C# 中使用 P/Invoke 完成,但这需要更多的工作。

如果您仍然特别希望能够将服务枚举为这些用户,则需要授予他们SC_MANAGER_ENUMERATE_SERVICE在 SCM 上的权限。不幸的是,根据 MSDN,SCM 的安全性只能在 Windows Server 2003 sp1 或更高版本上进行修改。

于 2008-10-17T05:08:56.780 回答
3

感谢查理的那行代码。这就是我最终要做的。我从这个网站得到了这个想法:http: //www.codeproject.com/KB/cs/svcmgr.aspx?display=Print

我还必须将我正在访问的帐户添加到服务器上的 Power Users 组中。

public static ServiceControllerStatus FindService()
        {
            ServiceControllerStatus status = ServiceControllerStatus.Stopped;
    try
            {
                string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
                string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower();

                ImpersonationUtil.Impersonate();

                ServiceController service = new ServiceController(serviceName, machineName);
                status = service.Status;
            }
            catch(Exception ex)
            {
                status = ServiceControllerStatus.Stopped;
                SaveError(ex, "Utilities - FindService()");
            }

            return status;
        }

这是我使用 ImpersonationUtil.Impersonate() 的另一个类:

public static class ImpersonationUtil
    {
        public static bool Impersonate()
        {
            string logon = ConfigurationManager.AppSettings["ImpersonationUserName"];
            string password = ConfigurationManager.AppSettings["ImpersonationPassword"];
            string domain = ConfigurationManager.AppSettings["ImpersonationDomain"];

            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;
            WindowsImpersonationContext impersonationContext = null;

            if (LogonUser(logon, domain, password, 2, 0, ref token) != 0)
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    impersonationContext = new WindowsIdentity(tokenDuplicate).Impersonate();
            //

            return (impersonationContext != null);
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
        public static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
        public extern static int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
    }
于 2008-10-17T15:34:19.093 回答
1

您可以尝试在 web.config 文件中使用 ASP.NET 模拟,并指定具有适当权限的用户帐户:

    <system.web>
       <identity impersonate="true" userName="Username" password="Password" />
    </system.web

查看MSDN 上的这篇文章。我相信还有其他选项不需要将密码存储在 web.config 文件中,例如将其放在注册表项中。

这将导致 ASP.NET 工作进程在指定用户的上下文而不是登录到 Web 应用程序的用户的上下文中运行。但是,这会带来安全问题,我会强烈重新考虑您的设计。您可能需要考虑让 ASP.NET 网页依次向其他实际控制服务的进程(甚至是另一个 Windows 服务)发出请求,或者将请求写入 Windows 服务定期轮询的数据库表。

于 2008-10-17T01:18:51.113 回答