ドメイン資格情報を検証する方法は?


86

ドメインコントローラーに対して一連の資格情報を検証したいと思います。例えば:

Username: STACKOVERFLOW\joel
Password: splotchy

方法1.偽装を使用してActiveDirectoryをクエリする

多くの人が、ActiveDirectoryに何かを照会することを提案しています。例外がスローされた場合は、資格情報が無効であることがわかります-このstackoverflowの質問で提案されているように。

ただし、このアプローチにはいくつかの重大な欠点があります。

  1. ドメインアカウントを認証するだけでなく、暗黙的な承認チェックも実行します。つまり、偽装トークンを使用してADからプロパティを読み取っています。それ以外の場合は有効なアカウントにADから読み取る権限がない場合はどうなりますか?デフォルトでは、すべてのユーザーが読み取りアクセス権を持っていますが、ドメインポリシーを設定して、制限されたアカウント(またはグループ)のアクセス許可を無効にすることができます。

  2. ADに対するバインドには重大なオーバーヘッドがあり、ADスキーマキャッシュをクライアントでロードする必要があります(DirectoryServicesが使用するADSIプロバイダーのADSIキャッシュ)。これはネットワークとADサーバーの両方であり、リソースを消費します。また、ユーザーアカウントの認証などの単純な操作にはコストがかかりすぎます。

  3. 例外ではない場合の例外の失敗に依存しており、それが無効なユーザー名とパスワードを意味すると想定しています。その他の問題(ネットワーク障害、AD接続障害、メモリ割り当てエラーなど)は、認証障害として誤って解釈されます。

方法2.LogonUser Win32 API

他の人が使用して示唆されているLogonUser()API関数を。これは良さそうに聞こえますが、残念ながら、呼び出し元のユーザーは、通常、オペレーティングシステム自体にのみ付与されるアクセス許可を必要とする場合があります。

LogonUserを呼び出すプロセスには、SE_TCB_NAME特権が必要です。呼び出しプロセスにこの特権がない場合、LogonUserは失敗し、GetLastErrorはERROR_PRIVILEGE_NOT_HELDを返します。

場合によっては、LogonUserを呼び出すプロセスで、SE_CHANGE_NOTIFY_NAME特権も有効にする必要があります。それ以外の場合、LogonUserは失敗し、GetLastErrorはERROR_ACCESS_DENIEDを返します。この特権は、ローカルシステムアカウントまたはadministratorsグループのメンバーであるアカウントには必要ありません。デフォルトでは、SE_CHANGE_NOTIFY_NAMEはすべてのユーザーに対して有効になっていますが、一部の管理者はすべてのユーザーに対して無効にすることができます。

「配っオペレーティングシステムの一部として、 Microsoftがで指摘するように- 」権限は行き当たりばったりやりたいものではありませんナレッジベースの記事

... LogonUserを呼び出すプロセスには、SE_TCB_NAME特権が必要です(ユーザーマネージャーでは、これは「オペレーティングシステムの一部として機能する」権限です)。SE_TCB_NAME特権は非常に強力であり、 資格情報を検証する必要があるアプリケーション実行できるようにするためだけに、任意のユーザーに付与しないでください

さらに、LogonUser()空白のパスワードが指定されている場合、への呼び出しは失敗します。


ドメイン資格情報のセットを認証する適切な方法は何ですか?


私は起こるのマネージコードから呼び出すことが、これは、AA、一般的なWindowsの質問です。お客様には.NETFramework2.0がインストールされていると想定できます。


1
読者は、Windows XP以降、LogonUserはSE_TCB_NAMEを必要としないことに注意する必要があります(Passportアカウントにログオンしている場合を除く)。
ハリージョンストン

回答:


130

System.DirectoryServices.AccountManagementを使用した.NET3.5のC#。

 bool valid = false;
 using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
 {
     valid = context.ValidateCredentials( username, password );
 }

これにより、現在のドメインに対して検証されます。他のオプションについては、パラメーター化されたPrincipalContextコンストラクターを確認してください。


@tvanfosson:DirectoryServicesはADを使用しませんか?
ミッチウィート

1
はい。ただし、ドキュメントには、これが資格情報を検証するための高速な方法であることが示されています。また、オブジェクトからプロパティを読み取っていないため、質問で説明したバインディングメソッドとも異なります。メソッドは、ディレクトリオブジェクトではなく、コンテキスト上にあることに注意してください。
tvanfosson 2008年

修正:System.DirectoryServices.AccountManagementには.NET3.5が必要です。(msdn.microsoft.com/en-us/library/...
イアン・ボイド

19
new PrincipalContext(ContextType.Machine)代わりに使用した場合は、ローカルユーザーでも機能します。
vansFannel 2014年

これがキャッシュされた資格情報で機能するかどうか、またはDCへの接続が必要かどうかを誰かが知っていますか?現在取り組んでいるいくつかの実装についてこれを知る必要がありますが、現在テストするドメインはありません
Jcl 2016年

21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.DirectoryServices.AccountManagement;

public struct Credentials
{
    public string Username;
    public string Password;
}

public class Domain_Authentication
{
    public Credentials Credentials;
    public string Domain;

    public Domain_Authentication(string Username, string Password, string SDomain)
    {
        Credentials.Username = Username;
        Credentials.Password = Password;
        Domain = SDomain;
    }

    public bool IsValid()
    {
        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
        {
            // validate the credentials
            return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
        }
    }
}

7
これには、3年前の@tvanfossonの回答との大きな違いが含まれていますか?
gbjbaanb 2013年

5
@gbjbaanbはい、Domain作成時のパラメーターが含まれているのでPrincipalContext、私が知りたいと思っていて、この回答で見つけたものです。
Rudi Visser 2015年

1
@RudiVisser tvanfossonは、「他のオプションについては、パラメーター化されたPrincipalContextコンストラクターを確認してください」と提案しました。常にドキュメントを読んでください。インターネットの言葉だけを使ってはいけません。:)
gbjbaanb 2015年

4
@gbjbaanbはい、もちろん、他の場所で読むためにではなく、リンクや提案よりも、実施例を提供することが、我々は答えの複数の応募を受け付け理由ですStackOverflowのマントラです:Dは、単純に、これはと言うより提供しています。
Rudi Visser 2015年

UWPアプリで同様のことを行う方法を知っている人はいますか?(Azure ADではなく通常のADを使用)。私はここで質問をしました:stackoverflow.com/questions/42821447
slayernoah 2017年

7

次のコードを使用して資格情報を検証しています。以下に示す方法で、資格情報が正しいかどうか、パスワードの有効期限が切れているかどうか、または変更が必要かどうかを確認します。

私は何年もの間このようなものを探していました...だから私はこれが誰かを助けることを願っています!

using System;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Runtime.InteropServices;

namespace User
{
    public static class UserValidation
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool LogonUser(string principal, string authority, string password, LogonTypes logonType, LogonProviders logonProvider, out IntPtr token);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);
        enum LogonProviders : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35,     // sends smoke signals to authority
            WinNT40,     // uses NTLM
            WinNT50      // negotiates Kerb or NTLM
        }
        enum LogonTypes : uint
        {
            Interactive = 2,
            Network = 3,
            Batch = 4,
            Service = 5,
            Unlock = 7,
            NetworkCleartext = 8,
            NewCredentials = 9
        }
        public  const int ERROR_PASSWORD_MUST_CHANGE = 1907;
        public  const int ERROR_LOGON_FAILURE = 1326;
        public  const int ERROR_ACCOUNT_RESTRICTION = 1327;
        public  const int ERROR_ACCOUNT_DISABLED = 1331;
        public  const int ERROR_INVALID_LOGON_HOURS = 1328;
        public  const int ERROR_NO_LOGON_SERVERS = 1311;
        public  const int ERROR_INVALID_WORKSTATION = 1329;
        public  const int ERROR_ACCOUNT_LOCKED_OUT = 1909;      //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!!
        public  const int ERROR_ACCOUNT_EXPIRED = 1793;
        public  const int ERROR_PASSWORD_EXPIRED = 1330;

        public static int CheckUserLogon(string username, string password, string domain_fqdn)
        {
            int errorCode = 0;
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD"))
            {
                if (!pc.ValidateCredentials(username, password))
                {
                    IntPtr token = new IntPtr();
                    try
                    {
                        if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token))
                        {
                            errorCode = Marshal.GetLastWin32Error();
                        }
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                    finally
                    {
                        CloseHandle(token);
                    }
                }
            }
            return errorCode;
        }
    }

これは質問で説明されている「方法2」です...だから...実際には質問に答えていません
Robert Levy

1

ローカルユーザーを特定する方法は次のとおりです。

    public bool IsLocalUser()
    {
        return windowsIdentity.AuthenticationType == "NTLM";
    }

イアン・ボイドによる編集

NTLMはもう使用しないでください。これは非常に古く、非常に悪いため、MicrosoftのApplication Verifier(一般的なプログラミングミスをキャッチするために使用されます)は、NTLMを使用していることを検出すると、警告をスローします。

誰かが誤ってNTLMを使用している場合にテストを行う理由について、ApplicationVerifierのドキュメントの章を次に示します。

NTLMプラグインが必要な理由

NTLMは、アプリケーションとオペレーティングシステムのセキュリティを危険にさらす可能性のある欠陥がある古い認証プロトコルです。最も重要な欠点は、サーバー認証がないことです。これにより、攻撃者がユーザーをだましてなりすましサーバーに接続させる可能性があります。欠落しているサーバー認証の結果として、NTLMを使用するアプリケーションは、「リフレクション」攻撃と呼ばれるタイプの攻撃に対して脆弱になる可能性もあります。この後者により、攻撃者はユーザーの認証会話を正当なサーバーに乗っ取り、それを使用してユーザーのコンピューターに対して攻撃者を認証することができます。NTLMの脆弱性とそれらを悪用する方法は、セキュリティコミュニティでの研究活動を増やすことのターゲットです。

Kerberosは長年利用可能ですが、多くのアプリケーションは依然としてNTLMのみを使用するように作成されています。これにより、アプリケーションのセキュリティが不必要に低下します。ただし、KerberosはすべてのシナリオでNTLMを置き換えることはできません。主に、クライアントがドメインに参加していないシステム(おそらくこれらの中で最も一般的なホームネットワーク)に対して認証する必要があるシナリオです。Negotiateセキュリティパッケージは、可能な限りKerberosを使用し、他のオプションがない場合にのみNTLMに戻る下位互換性のある侵害を許可します。NTLMの代わりにNegotiateを使用するようにコードを切り替えると、アプリケーションの互換性がほとんどまたはまったく導入されない一方で、お客様のセキュリティが大幅に向上します。ネゴシエート自体は特効薬ではありません。攻撃者がNTLMへのダウングレードを強制できる場合がありますが、これらを悪用するのは非常に困難です。ただし、すぐに改善される点の1つは、Negotiateを正しく使用するように作成されたアプリケーションは、NTLMリフレクション攻撃の影響を自動的に受けないことです。

NTLMの使用に対する最後の注意点として、Windowsの将来のバージョンでは、オペレーティングシステムでNTLMの使用を無効にすることが可能になる予定です。アプリケーションがNTLMに強く依存している場合、NTLMが無効になっていると、アプリケーションは認証に失敗します。

プラグインのしくみ

Verifierプラグは、次のエラーを検出します。

  • NTLMパッケージは、AcquireCredentialsHandle(またはより高いレベルのラッパーAPI)の呼び出しで直接指定されます。

  • InitializeSecurityContextの呼び出しのターゲット名がNULLです。

  • InitializeSecurityContextの呼び出しのターゲット名は、適切に形成されたSPN、UPN、またはNetBIOSスタイルのドメイン名ではありません。

後者の2つのケースでは、Negotiateが直接(最初のケース)または間接的にNTLMにフォールバックします(2番目のケースではドメインコントローラーが「principalnot found」エラーを返し、Negotiateがフォールバックします)。

プラグインは、NTLMへのダウングレードを検出したときにも警告をログに記録します。たとえば、ドメインコントローラーによってSPNが見つからない場合です。これらは、ドメインに参加していないシステムに対して認証する場合など、正当なケースであることが多いため、警告としてのみログに記録されます。

NTLMが停止します

5000 –アプリケーションが明示的に選択したNTLMパッケージ

重大度–エラー

アプリケーションまたはサブシステムは、AcquireCredentialsHandleの呼び出しで、ネゴシエートではなくNTLMを明示的に選択します。クライアントとサーバーがKerberosを使用して認証できる場合でも、NTLMを明示的に選択することでこれを防ぐことができます。

このエラーを修正する方法

このエラーの修正は、NTLMの代わりにネゴシエートパッケージを選択することです。これがどのように行われるかは、クライアントまたはサーバーによって使用されている特定のネットワークサブシステムによって異なります。いくつかの例を以下に示します。使用している特定のライブラリまたはAPIセットに関するドキュメントを参照する必要があります。

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”

-1
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices.AccountManagement;

class WindowsCred
{
    private const string SPLIT_1 = "\\";

    public static bool ValidateW(string UserName, string Password)
    {
        bool valid = false;
        string Domain = "";

        if (UserName.IndexOf("\\") != -1)
        {
            string[] arrT = UserName.Split(SPLIT_1[0]);
            Domain = arrT[0];
            UserName = arrT[1];
        }

        if (Domain.Length == 0)
        {
            Domain = System.Environment.MachineName;
        }

        using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Domain)) 
        {
            valid = context.ValidateCredentials(UserName, Password);
        }

        return valid;
    }
}

Kashif Mushtaqオタワ、カナダ


System.DirectoryServices.AccountManagement名前空間は.NET3.5で新しく追加されました
Jeremy Grey

1
これは4年近く前のことですが、ローカルユーザーを検証する場合は、PrincipalContextを作成するときにContextTypeをContextType.Machineに設定する必要があります。それ以外の場合は、Domain変数で指定されたマシン名が実際にはドメインサーバーであると見なされます。
SolidRegardless 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.