5

Windows 認証が有効になっている Web サイトがあります。Web サイトのページから、ユーザーはデータベースを操作するサービスを開始できます。

私はサーバーのローカル管理者であるため、サービスを開始しても問題ありません。しかし、ユーザーにテストしてもらいましたが、サービスを開始できません。

私の質問は:


現在ログインしているアカウントとは異なる 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 ブロックの 2 行目です。エラーは次のとおりです。

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 で説明されています

対象のユーザーが "Remote Service Controllers" グループ (作成する) に属し、サービス名が "my-service-name" であると仮定して、このセットアップを実行する C++ コードを次に示します。作成したグループではなく、Users などの既知のグループ (必ずしも良い考えではありません) にアクセス権を付与する場合は、 に変更する必要があることに注意してTRUSTEE_IS_GROUPくださいTRUSTEE_IS_WELL_KNOWN_GROUP

コードには、追加したいエラー チェックがありません。失敗する可能性のある 3 つの関数 (Get/SetNamedSecurityInfo および SetEntriesInAcl) はすべて、成功を示すために 0 を返します。

別の注意: %WINDIR%\System32 の下にあるSC ツールを使用して、サービスのセキュリティ記述子を設定することもできますが、プログラミングは必要ありません。

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

これは P/Invoke を使用して C# から実行することもできますが、それにはもう少し手間がかかります。

これらのユーザーとしてサービスを列挙できるようにしたい場合はSC_MANAGER_ENUMERATE_SERVICE、SCM で権限を付与する必要があります。残念ながら、MSDN によると、SCM のセキュリティは Windows Server 2003 sp1 以降でのみ変更できます。

于 2008-10-17T05:08:56.780 に答える
3

コード Charlie の行をありがとう。これが私がやったことです。この Web サイトからアイデアを得ました: 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ファイルにパスワードを保存する必要のない他のオプションがあると思います。

これにより、Web アプリケーションにログインしているユーザーではなく、指定されたユーザーのコンテキストで ASP.NET ワーカー プロセスが実行されます。ただし、これはセキュリティ上の問題を引き起こすため、設計を強く再考します。ASP.NET Web ページで、サービスを実際に制御する他のプロセス (別の Windows サービスであっても) に要求を送信するか、Windows サービスが定期的にポーリングするデータベース テーブルに要求を書き込むことを検討することをお勧めします。

于 2008-10-17T01:18:51.113 に答える