¿Cómo validar las credenciales de dominio?

Quiero validar un conjunto de credenciales contra el controlador de dominio. p.ej:

Username: STACKOVERFLOW\joel Password: splotchy 

Método 1. Consultar Active Directory con suplantación

Mucha gente sugiere consultar el Active Directory por algo. Si se lanza una excepción, entonces sabrá que las credenciales no son válidas, como se sugiere en esta pregunta de stackoverflow .

Sin embargo, hay algunos inconvenientes serios en este enfoque :

  1. No solo está autenticando una cuenta de dominio, sino que también está realizando una verificación de autorización implícita. Es decir, está leyendo propiedades del AD usando un token de suplantación. ¿Qué pasa si la cuenta válida no tiene derechos para leer de la AD? De forma predeterminada, todos los usuarios tienen acceso de lectura, pero las políticas de dominio se pueden configurar para deshabilitar los permisos de acceso para cuentas restringidas (y / o grupos).

  2. La vinculación en contra del AD tiene una sobrecarga importante, el caché de esquema AD debe cargarse en el cliente (caché ADSI en el proveedor ADSI utilizado por DirectoryServices). Esto es tanto de red como de servidor AD, que consume muchos recursos, y es demasiado costoso para una operación simple como la autenticación de una cuenta de usuario.

  3. Confía en un error de excepción para un caso no excepcional y, suponiendo que eso signifique un nombre de usuario y contraseña no válidos. Otros problemas (por ejemplo, falla de red, falla de conectividad de AD, error de asignación de memoria, etc.) se interpretan erróneamente como fallas de autenticación.

Método 2. LogonUser Win32 API

Otros han sugerido usar la función API de LogonUser() . Esto suena bien, pero desafortunadamente el usuario que llama a veces necesita un permiso generalmente solo otorgado al sistema operativo mismo:

El proceso que llama a LogonUser requiere el privilegio SE_TCB_NAME. Si el proceso de llamada no tiene este privilegio, LogonUser falla y GetLastError devuelve ERROR_PRIVILEGE_NOT_HELD.

En algunos casos, el proceso que llama a LogonUser también debe tener habilitado el privilegio SE_CHANGE_NOTIFY_NAME; de lo contrario, LogonUser falla y GetLastError devuelve ERROR_ACCESS_DENIED. Este privilegio no es necesario para la cuenta del sistema local o las cuentas que son miembros del grupo de administradores. De forma predeterminada, SE_CHANGE_NOTIFY_NAME está habilitado para todos los usuarios, pero algunos administradores pueden deshabilitarlo para todos.

La distribución del privilegio ” Actuar como parte del sistema operativo ” no es algo que desee hacer de cualquier manera, como señala Microsoft en un artículo de la base de conocimiento :

… el proceso que llama a LogonUser debe tener el privilegio SE_TCB_NAME (en el Administrador de usuarios, este es el derecho ” Actuar como parte del sistema operativo “). El privilegio SE_TCB_NAME es muy poderoso y no debe otorgarse a ningún usuario arbitrario solo para que pueda ejecutar una aplicación que necesita validar las credenciales.

Además, una llamada a LogonUser() fallará si se especifica una contraseña en blanco.


¿Cuál es la forma correcta de autenticar un conjunto de credenciales de dominio?


Resulta que estoy llamando desde código administrado, pero esta es una pregunta general de Windows. Se puede suponer que los clientes tienen instalado .NET Framework 2.0.

C # en .NET 3.5 utilizando System.DirectoryServices.AccountManagement .

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

Esto validará contra el dominio actual. Consulte el constructor de PrincipalContext parametrizado para otras opciones.

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

Estoy usando el siguiente código para validar las credenciales. El método que se muestra a continuación confirmará si las credenciales son correctas y, en caso de que no sea así, la contraseña ha caducado o si necesita modificaciones.

He estado buscando algo como esto por años … ¡Así que espero que esto ayude a alguien!

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

A continuación, se explica cómo determinar un usuario local:

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

Editado por Ian Boyd

Ya no deberías usar NTLM. Es tan viejo, y tan malo, que Application Verifier de Microsoft (que se utiliza para detectar errores de progtwigción comunes) lanzará una advertencia si lo detecta usando NTLM.

Aquí hay un capítulo de la documentación de Application Verifier sobre por qué tienen una prueba si alguien está usando NTLM por error:

Por qué se necesita el complemento NTLM

NTLM es un protocolo de autenticación obsoleto con fallas que pueden comprometer la seguridad de las aplicaciones y el sistema operativo. La falla más importante es la falta de autenticación del servidor, que podría permitir a un atacante engañar a los usuarios para que se conecten a un servidor falsificado. Como corolario de la falta de autenticación del servidor, las aplicaciones que usan NTLM también pueden ser vulnerables a un tipo de ataque conocido como ataque de “reflexión”. Esto último permite a un atacante secuestrar la conversación de autenticación de un usuario a un servidor legítimo y utilizarla para autenticar al atacante en la computadora del usuario. Las vulnerabilidades de NTLM y las formas de explotarlos son el objective de boost la actividad de investigación en la comunidad de seguridad.

Aunque Kerberos ha estado disponible durante muchos años, muchas aplicaciones todavía se escriben para usar NTLM únicamente. Esto reduce innecesariamente la seguridad de las aplicaciones. Sin embargo, Kerberos no puede reemplazar NTLM en todos los escenarios, principalmente aquellos en los que un cliente necesita autenticarse en sistemas que no están unidos a un dominio (una red doméstica tal vez sea la más común). El paquete de seguridad Negotiate permite un compromiso retrocompatible que usa Kerberos siempre que sea posible y solo revierte a NTLM cuando no hay otra opción. Cambiar el código para usar Negociar en lugar de NTLM boostá significativamente la seguridad para nuestros clientes al tiempo que presenta pocas o ninguna compatibilidad de aplicaciones. Negociar por sí mismo no es una panacea: hay casos en los que un atacante puede forzar la degradación a NTLM, pero estos son significativamente más difíciles de explotar. Sin embargo, una mejora inmediata es que las aplicaciones escritas para usar Negociar correctamente son automáticamente inmunes a los ataques de reflexión NTLM.

A modo de advertencia final contra el uso de NTLM: en versiones futuras de Windows será posible deshabilitar el uso de NTLM en el sistema operativo. Si las aplicaciones tienen una dependencia fuerte en NTLM, simplemente no podrán autenticarse cuando NTLM esté desactivado.

Cómo funciona el complemento

El conector Verifier detecta los siguientes errores:

  • El paquete NTLM se especifica directamente en la llamada a AcquireCredentialsHandle (o API de envoltura de nivel superior).

  • El nombre de destino en la llamada a InitializeSecurityContext es NULL.

  • El nombre de destino en la llamada a InitializeSecurityContext no es un nombre de dominio SPN, UPN o estilo NetBIOS formado correctamente.

Los últimos dos casos obligarán a Negotiate a recurrir a NTLM, ya sea directamente (el primer caso) o indirectamente (el controlador de dominio devolverá un error de “principal no encontrado” en el segundo caso, haciendo que Negotiate retroceda).

El complemento también registra advertencias cuando detecta degradaciones a NTLM; por ejemplo, cuando el controlador de dominio no encuentra un SPN. Estos solo se registran como advertencias, ya que a menudo son casos legítimos, por ejemplo, cuando se autentica en un sistema que no está unido al dominio.

NTLM se detiene

5000 – La aplicación tiene un paquete NTLM explícitamente seleccionado

Gravedad – Error

La aplicación o subsistema selecciona explícitamente NTLM en lugar de Negociar en la llamada a AcquireCredentialsHandle. Aunque es posible que el cliente y el servidor se autentiquen usando Kerberos, esto se evita mediante la selección explícita de NTLM.

Cómo arreglar este error

La solución para este error es seleccionar el paquete Negociar en lugar de NTLM. Cómo se hace esto dependerá del subsistema de red particular que esté utilizando el cliente o servidor. Algunos ejemplos se dan a continuación. Debe consultar la documentación en la biblioteca particular o conjunto de API que está utilizando.

 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 Ottawa, Canadá