如何validation域凭据?

我想validation对域控制器的一组凭据。 例如:

Username: STACKOVERFLOW\joel Password: splotchy 

方法1.用模拟查询Active Directory

很多人build议查询Active Directory的东西。 如果抛出exception,那么你知道凭据是无效的 – 正如在这个stackoverflow问题中所build议的。

然而,这种方法有一些严重的缺陷 :

  1. 您不仅要validation域帐户,还要进行隐式授权检查。 也就是说,您正在使用模拟令牌从AD读取属性。 如果否则有效的帐户没有权利从AD读取? 默认情况下,所有用户都具有读取访问权限,但可以将域策略设置为禁用受限帐户(和/或组)的访问权限。

  2. 针对AD的绑定具有严重开销,AD架构caching必须在客户端(DirectoryServices使用的ADSI提供程序中的ADSIcaching)中加载。 这既是networking,又是AD服务器,消耗资源,而且对于像validation用户帐户这样的简单操作来说太昂贵了。

  3. 对于非例外情况,您依赖exception失败,并假设这意味着无效的用户名和密码。 其他问题(如networking故障,AD连接故障,内存分配错误等)则被误解为authentication失败。

方法2. LogonUser Win32 API

其他人build议使用LogonUser() API函数。 这听起来不错,但不幸的是,主叫用户有时需要一个通常只给予操作系统本身的权限:

调用LogonUser的进程需要SE_TCB_NAME特权。 如果调用进程没有此权限,LogonUser将失败,并且GetLastError返回ERROR_PRIVILEGE_NOT_HELD。

在某些情况下,调用LogonUser的进程也必须启用SE_CHANGE_NOTIFY_NAME权限; 否则,LogonUser失败,GetLastError返回ERROR_ACCESS_DENIED。 作为pipe理员组成员的本地系统帐户或帐户不需要此权限。 默认情况下,所有用户都启用了SE_CHANGE_NOTIFY_NAME,但是一些pipe理员可能会禁用所有用户。

正如微软在一篇知识库文章中指出的那样,将“ 行为作为操作系统的一部分 ”特权发出是不是你想做的事情:

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

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


什么是validation一组域证书的正确方法?


碰巧从托pipe代码调用,但这是一个一般的Windows问题。 可以假定客户安装了.NET Framework 2.0。

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

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

这将根据当前的域进行验证。 查看其他选项的参数化PrincipalContext构造函数。

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

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

我一直在寻找这样的年龄…所以我希望这可以帮助别人!

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

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

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

编辑伊恩·博伊德

你不应该再使用NTLM了。 微软的应用程序验证程序(用于捕获常见编程错误)如果发现您使用的是NTLM,将会发出警告,它会很老,而且很糟糕。

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

为什么需要NTLM插件

NTLM是一种过时的身份验证协议,其漏洞可能会危及应用程序和操作系统的安全。 最重要的缺点是缺乏服务器身份验证,这可能会导致攻击者诱骗用户连接到欺骗服务器。 作为缺少服务器身份验证的必然结果,使用NTLM的应用程序也容易受到称为“反射”攻击的攻击类型的攻击。 后者允许攻击者将用户的身份验证对话劫持到合法的服务器,并用它来向用户的计算机验证攻击者。 NTLM的漏洞和利用这些漏洞的方法是增加安全社区研究活动的目标。

虽然Kerberos已经有很多年了,但许多应用程序仍然只是使用NTLM编写的。 这不必要地降低了应用程序的安全性。 但Kerberos无法在所有情况下取代NTLM – 主要是客户端需要对未加入域的系统进行身份验证(家庭网络可能是最常见的)。 Negotiate安全软件包允许尽可能使用Kerberos的向后兼容的安全措施,只有在没有其他选项的情况下才会恢复为NTLM。 切换代码以使用Negotiate而不是NTLM将显着提高我们的客户的安全性,同时引入很少或没有应用程序兼容性。 谈判本身并不是一个银弹 – 有些情况下,攻击者可以强制降级到NTLM,但这些要难得多。 但是,一个直接的改进是编写正确使用协商的应用程序自动免疫NTLM反射攻击。

通过对使用NTLM谨慎的最后一句话: 在未来的Windows版本中,可以禁止在操作系统上使用NTLM。 如果应用程序对NTLM有很大的依赖性,那么当NTLM被禁用时,它们只是无法进行身份验证。

插件的工作原理

验证程序插件检测到以下错误:

  • 在调用AcquireCredentialsHandle(或更高级别的包装器API)时直接指定NTLM包。

  • InitializeSecurityContext调用中的目标名称为NULL。

  • InitializeSecurityContext调用中的目标名称不是正确形成的SPN,UPN或NetBIOS风格的域名。

后两种情况下强制协商直接返回到NTLM(第一种情况)或间接(域控制器将在第二种情况下返回“主体未找到”错误,导致协商回退)。

检测到降级到NTLM时,插件还会记录警告; 例如,域控制器未找到SPN时。 这些只会被记录为警告,因为它们通常是合法的情况 – 例如,对未加入域的系统进行身份验证时。

NTLM停止

5000 – 应用程序已明确选择NTLM程序包

严重性 – 错误

应用程序或子系统在调用AcquireCredentialsHandle时显式选择NTLM而不是Negotiate。 尽管客户端和服务器可能使用Kerberos进行身份验证,但是通过显式选择NTLM来防止这种情况。

如何解决这个错误

此错误的修复程序是选择协商包来代替NTLM。 如何完成这将取决于客户端或服务器正在使用的特定网络子系统。 下面给出一些例子。 您应该查阅您正在使用的特定库或API集的文档。

 APIs(parameter) Used by Application Incorrect Value Correct Value ===================================== =============== ======================== AcquireCredentialsHandle (pszPackage) “NTLM” NEGOSSP_NAME “Negotiate” 
 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