首页 文章

如何验证域凭据?

提问于
浏览
76

我想针对域控制器验证一组凭据 . 例如 . :

Username: STACKOVERFLOW\joel
Password: splotchy

方法1.使用模拟查询Active Directory

很多人建议查询Active Directory . 如果抛出异常,则表示凭据无效 - 如this stackoverflow question中所述 .

然而,有一些严重的drawbacks to this approach

  • 您不仅要对域帐户进行身份验证,还要进行隐式授权检查 . 也就是说,您正在使用模拟令牌从AD中读取属性 . 如果其他有效帐户无权从AD读取,该怎么办?默认情况下,所有用户都具有读取权限,但可以将域策略设置为禁用受限帐户(和/或组)的访问权限 .

  • 绑定AD会产生严重的开销,必须在客户端加载AD架构缓存(DirectoryServices使用的ADSI提供程序中的ADSI缓存) . 这既是网络又是AD服务器,消耗资源 - 而且对于像验证用户帐户这样的简单操作来说太昂贵了 .

  • 您依赖于非例外情况的异常失败,并假设这意味着无效的用户名和密码 . 然后,其他问题(例如,网络故障,AD连接故障,内存分配错误等)被错误地表示为身份验证失败 .

方法2. LogonUser Win32 API

Others建议使用LogonUser() API函数 . 这听起来不错,但不幸的是,调用用户有时需要一个权限,通常只给操作系统本身:

调用LogonUser的进程需要SE_TCB_NAME权限 . 如果调用进程没有此权限,LogonUser将失败,GetLastError将返回ERROR_PRIVILEGE_NOT_HELD . 在某些情况下,调用LogonUser的进程还必须启用SE_CHANGE_NOTIFY_NAME权限;否则,LogonUser失败,GetLastError返回ERROR_ACCESS_DENIED . 作为管理员组成员的本地系统帐户或帐户不需要此权限 . 默认情况下,为所有用户启用SE_CHANGE_NOTIFY_NAME,但某些管理员可能会为所有用户禁用它 .

正如微软在_2486560中指出的那样,发布“作为操作系统的一部分”特权并不是你想要做的事情 .

...调用LogonUser的进程必须具有SE_TCB_NAME权限(在用户管理器中,这是“作为操作系统的一部分”权限) . SE_TCB_NAME权限非常强大,不应授予任何任意用户,以便他们可以运行需要验证凭据的应用程序 .

此外,如果指定了空密码,则对 LogonUser() 的调用将失败 .


验证一组域凭据的正确方法是什么?


我碰巧是从托管代码调用,但这是一个普通的Windows问题 . 可以假设客户已安装.NET Framework 2.0 .

5 回答

  • 116

    .NET 3.5中的C#使用System.DirectoryServices.AccountManagement .

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

    这将验证当前域 . 查看参数化的PrincipalContext构造函数以获取其他选项 .

  • 1
    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);
            }
        }
    }
    
  • 16

    我使用以下代码来验证凭据 . 下面显示的方法将确认凭据是否正确,如果密码已过期或需要更改 .

    我一直在寻找这样的东西...所以我希望这有助于某人!

    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;
            }
        }
    
  • -1

    以下是如何确定本地用户:

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

    由Ian Boyd编辑

    你不应该再使用NTLM了 . 微软的应用程序验证程序(用于捕获常见的编程错误)如果它检测到你使用NTLM就会发出警告,这是如此古老,如此糟糕 .

    以下是Application Verifier文档中的一章,说明如果有人错误地使用NTLM,他们为什么要进行测试:

    为什么需要NTLM插件NTLM是一种过时的身份验证协议,其缺陷可能会危及应用程序和操作系统的安全性 . 最重要的缺点是缺乏服务器身份验证,这可能允许攻击者诱骗用户连接到欺骗性服务器 . 作为缺少服务器身份验证的必然结果,使用NTLM的应用程序也可能容易受到一种称为“反射”攻击的攻击 . 后者允许攻击者劫持用户的身份验证与合法服务器的对话,并使用它来验证攻击者对用户的计算机 . NTLM的漏洞和利用它们的方式是增加安全社区研究活动的目标 . 虽然Kerberos已经存在多年,但仍然编写许多应用程序以仅使用NTLM . 这不必要地降低了应用程序的安全性 . 但是,Kerberos无法在所有场景中替换NTLM - 主要是客户端需要对未加入域的系统(家庭网络可能是其中最常见的)进行身份验证的场景 . Negotiate安全包允许尽可能使用Kerberos的向后兼容折衷,并且在没有其他选项时仅恢复为NTLM . 切换代码以使用Negotiate而不是NTLM将显着提高我们客户的安全性,同时引入很少或没有应用程序兼容性 . 谈判本身并不是一个灵丹妙药 - 有些情况下,攻击者可以强制降级到NTLM,但这些更难以利用 . 但是,立即改进的是,为正确使用Negotiate而编写的应用程序自动免受NTLM反射攻击 . 通过对使用NTLM的最后警告:在Windows的未来版本中,可以在操作系统中禁用NTLM . 如果应用程序对NTLM具有硬依赖性,则在禁用NTLM时,它们将无法进行身份验证 . 插件的工作原理Verifier插件检测到以下错误:在调用AcquireCredentialsHandle(或更高级别的包装器API)时直接指定了NTLM包 . InitializeSecurityContext调用中的目标名称为NULL . 调用InitializeSecurityContext时的目标名称不是正确形成的SPN,UPN或NetBIOS样式的域名 . 后两种情况将迫使Negotiate直接(第一种情况)或间接地回退到NTLM(在第二种情况下,域控制器将返回“未找到主体”错误,导致Negotiate回退) . 插件在检测到降级到NTLM时也会记录警告;例如,域控制器找不到SPN时 . 这些仅作为警告记录,因为它们通常是合法的情况 - 例如,在对未加入域的系统进行身份验证时 . NTLM停止5000 - 应用程序已明确选择NTLM程序包严重性 - 错误应用程序或子系统在调用AcquireCredentialsHandle时显式选择NTLM而不是Negotiate . 尽管客户端和服务器可能使用Kerberos进行身份验证,但这可以通过显式选择NTLM来防止 . 如何修复此错误此错误的修复是选择Negotiate包代替NTLM . 如何完成此操作取决于客户端或服务器使用的特定网络子系统 . 下面给出一些例子 . 您应该查阅您正在使用的特定库或API集的文档 . API(参数)由应用程序使用不正确的值正确值
    ===================================== ============= == ========================
    AcquireCredentialsHandle(pszPackage)“NTLM”NEGOSSP_NAME“Negotiate”

  • 5
    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渥太华,加拿大

相关问题