如何验证域凭据?

时间:2008-11-28 22:35:41

标签: c# windows security authentication

我想针对域控制器验证一组凭据。 e.g:

Username: STACKOVERFLOW\joel
Password: splotchy

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

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

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

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

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

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

  4. 方法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是   为所有用户启用,但有些用户启用   管理员可以禁用它   每个人。

    将“法案作为操作系统的一部分”特权发送出去并不是你想要做的事情 - 正如微软在knowledge base article中所指出的那样:

      

    ......正在呼叫的过程   LogonUser必须拥有SE_TCB_NAME   特权(在用户管理器中,这是   “作为运营的一部分   系统“正确”.SE_TCB_NAME   特权是非常强大的   不应该被授予任何任意用户,只是为了他们可以   运行需要的应用程序   验证凭证。

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


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


    发生从托管代码调用,但这是一个常见的Windows问题。可以假设客户已安装.NET Framework 2.0。

5 个答案:

答案 0 :(得分:118)

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

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

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

答案 1 :(得分:17)

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

答案 2 :(得分:5)

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

我多年来一直在寻找这样的东西......所以我希望这可以帮助别人!

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

答案 3 :(得分: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集的文档。

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

答案 4 :(得分:-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 加拿大渥太华