80 votes

Comment valider les identifiants de domaine?

Je veux valider un ensemble de justificatifs auprès du contrôleur de domaine. p. ex.:

Nom d'utilisateur : STACKOVERFLOW\joel
Mot de passe : splotchy

Méthode 1. Interroger Active Directory avec Impersonation

Beaucoup de gens suggèrent d'interroger l'Active Directory pour quelque chose. Si une exception est levée, alors vous savez que les justificatifs ne sont pas valides - comme suggéré dans cette question stackoverflow.

Il y a cependant quelques sérieux inconvénients à cette approche:

  1. Vous n'authentifiez pas seulement un compte de domaine, mais vous faites aussi une vérification d'autorisation implicite. C'est-à-dire, vous lisez des propriétés de l'AD en utilisant un jeton d'impersonation. Que se passe-t-il si le compte par ailleurs valide n'a pas le droit de lecture de l'AD ? Par défaut, tous les utilisateurs ont un accès en lecture, mais des politiques de domaine peuvent être définies pour désactiver les autorisations d'accès pour les comptes (et/ou groupes) restreints.

  2. Le binding contre l'AD a un sérieux surcoût, le cache du schéma AD doit être chargé au client (cache ADSI dans le fournisseur ADSI utilisé par DirectoryServices). Cela consomme à la fois des ressources réseau et du serveur AD - et est trop coûteux pour une opération simple comme l'authentification d'un compte utilisateur.

  3. Vous vous appuyez sur un échec d'exception pour un cas non exceptionnel, et en supposant que cela signifie un nom d'utilisateur et un mot de passe invalides. D'autres problèmes (p. ex. échec du réseau, échec de la connectivité AD, erreur d'allocation de mémoire, etc) sont alors mal interprétés comme un échec d'authentification.

Méthode 2. API Win32 LogonUser

D'autres ont suggéré d'utiliser la fonction API LogonUser(). Cela semble bien, mais malheureusement l'utilisateur appelant a parfois besoin d'une permission généralement donnée uniquement au système d'exploitation lui-même :

Le processus appelant LogonUser nécessite le privilège SE_TCB_NAME. Si le processus appelant n'a pas ce privilège, LogonUser échoue et GetLastError renvoie ERROR_PRIVILEGE_NOT_HELD.

Dans certains cas, le processus qui appelle LogonUser doit également avoir le privilège SE_CHANGE_NOTIFY_NAME activé ; sinon, LogonUser échoue et GetLastError renvoie ERROR_ACCESS_DENIED. Ce privilège n'est pas requis pour le compte système local ou les comptes qui sont membres du groupe des administrateurs. Par défaut, SE_CHANGE_NOTIFY_NAME est activé pour tous les utilisateurs, mais certains administrateurs peuvent le désactiver pour tout le monde.

Accorder le privilège "Agir comme une partie du système d'exploitation" n'est pas quelque chose que vous voulez faire à la légère - comme le souligne Microsoft dans un article de la base de connaissances :

...le processus qui appelle LogonUser doit avoir le privilège SE_TCB_NAME (dans Gestionnaire d'utilisateurs, c'est le droit "Agir comme partie du système d'exploitation"). Le privilège SE_TCB_NAME est très puissant et ne doit pas être accordé à n'importe quel utilisateur arbitraire juste pour qu'il puisse exécuter une application qui a besoin de valider des justificatifs.

De plus, un appel à LogonUser() échouera si un mot de passe vide est spécifié.


Quelle est la bonne façon d'authentifier un ensemble de justificatifs de domaine ?


Je happen à appeler à partir de code managé, mais il s'agit d'une question Windows générale. On peut supposer que les clients ont le .NET Framework 2.0 installé.

123voto

tvanfosson Points 268301

C# dans .NET 3.5 en utilisant System.DirectoryServices.AccountManagement.

 bool valide = false;
 using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
 {
     valide = context.ValidateCredentials( nom_utilisateur, mot_de_passe );
 }

Cela validera contre le domaine actuel. Consultez le constructeur PrincipalContext avec des paramètres pour d'autres options.

19voto

kantanomo Points 47

Installez System.DirectoryServices.AccountManagement à partir du Gestionnaire de paquets NuGet, puis :

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))
        {
            // valider les informations d'identification
            return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
        }
    }
}

7voto

Kevinrr3 Points 71

Je suis en train d'utiliser le code suivant pour valider les identifiants. La méthode ci-dessous confirmera si les identifiants sont corrects et, si ce n'est pas le cas, si le mot de passe a expiré ou nécessite un changement.

Je cherchais quelque chose comme ça depuis des siècles... Alors j'espère que cela aidera quelqu'un!

utilisant System;
utilisant System.DirectoryServices;
utilisant System.DirectoryServices.AccountManagement;
utilisant System.Runtime.InteropServices;

espace de noms 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, // par défaut pour la plate-forme (utilisez cela!)
            WinNT35,     // envoie des signaux de fumée à l'autorité
            WinNT40,     // utilise NTLM
            WinNT50      // négocie Kerb ou 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;      //Cela donne cette erreur si le compte est bloqué, INDÉPENDAMMENT DE LA VALIDITÉ DES IDENTIFIANTS FOURNIS!!!
        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;
            utilisant (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD"))
            {
                if (!pc.ValidateCredentials(username, password))
                {
                    IntPtr token = new IntPtr();
                    essayer
                    {
                        if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token))
                        {
                            errorCode = Marshal.GetLastWin32Error();
                        }
                    }
                    attraper (Exception)
                    {
                        lancer;
                    }
                    enfin
                    {
                        CloseHandle(token);
                    }
                }
            }
            retourner errorCode;
        }
    }

1voto

Alan Nicholas Points 11

Voici comment déterminer un utilisateur local :

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

Edité par Ian Boyd

Vous ne devriez plus du tout utiliser NTLM. C'est tellement vieux et si mauvais que l'Application Verifier de Microsoft (qui est utilisé pour détecter les erreurs de programmation courantes) lancera un avertissement s'il détecte que vous utilisez NTLM.

Voici un extrait de la documentation de l'Application Verifier expliquant pourquoi ils ont un test pour détecter si quelqu'un utilise NTLM par erreur :

Pourquoi le plug-in NTLM est nécessaire

NTLM est un protocole d'authentification obsolète avec des failles qui compromettent potentiellement la sécurité des applications et du système d'exploitation. La principale lacune est le manque d'authentification du serveur, ce qui pourrait permettre à un attaquant d'inciter les utilisateurs à se connecter à un serveur falsifié. En conséquence du manque d'authentification du serveur, les applications utilisant NTLM peuvent également être vulnérables à un type d'attaque appelé attaque "de réflection". Cette dernière permet à un attaquant de détourner la conversation d'authentification d'un utilisateur vers un serveur légitime et de l'utiliser pour s'authentifier auprès de l'ordinateur de l'utilisateur. Les vulnérabilités de NTLM et les méthodes pour les exploiter font l'objet d'une activité de recherche croissante au sein de la communauté de sécurité.

Bien que Kerberos soit disponible depuis de nombreuses années, de nombreuses applications sont encore écrites pour n'utiliser que NTLM. Cela réduit inutilement la sécurité des applications. Cependant, Kerberos ne peut pas remplacer NTLM dans tous les scénarios - principalement ceux où un client doit s'authentifier auprès de systèmes qui ne sont pas membres d'un domaine (un réseau domestique étant peut-être le plus courant parmi ceux-ci). Le package de sécurité "Negotiate" permet un compromis rétrocompatible qui utilise Kerberos chaque fois que possible et ne revient à NTLM que lorsqu'il n'y a pas d'autre option. Modifier le code pour utiliser Negotiate au lieu de NTLM augmentera considérablement la sécurité pour nos clients tout en introduisant peu ou pas de compatibilités d'application. Négocier par lui-même n'est pas une solution miracle - il y a des cas où un attaquant peut forcer le passage à NTLM mais ils sont nettement plus difficiles à exploiter. Cependant, une amélioration immédiate est que les applications écrites pour utiliser correctement Négocier sont automatiquement immunisées contre les attaques de réflection NTLM.

Pour donner un dernier avertissement contre l'utilisation de NTLM : dans les versions futures de Windows, il sera possible de désactiver l'utilisation de NTLM dans le système d'exploitation. Si les applications dépendent fortement de NTLM, elles échoueront simplement à s'authentifier lorsque NTLM sera désactivé.

Comment le plug-in fonctionne

Le plug-in Verifier détecte les erreurs suivantes :

  • Le package NTLM est directement spécifié dans l'appel à AcquireCredentialsHandle (ou une API d'emballage de niveau supérieur).

  • Le nom de la cible dans l'appel à InitializeSecurityContext est NULL.

  • Le nom de la cible dans l'appel à InitializeSecurityContext n'est pas un nom de SPN, de UPN ou de domaine au style NetBIOS correctement formé.

Les deux derniers cas forceront Negotiate à revenir à NTLM directement (dans le premier cas) ou indirectement (le contrôleur de domaine renverra une erreur "principale introuvable" dans le deuxième cas, forçant Negotiate à revenir en arrière).

Arrêts NTLM

5000 - Application a explicitement sélectionné le package NTLM

Sévérité - Erreur

L'application ou le sous-système sélectionne explicitement NTLM au lieu de Négociation dans l'appel à AcquireCredentialsHandle. Même s'il serait possible pour le client et le serveur de s'authentifier en utilisant Kerberos, cela est empêché par la sélection explicite de NTLM.

Comment corriger cette erreur

La correction de cette erreur consiste à sélectionner le package Négociation à la place de NTLM. La manière de faire dépendra du sous-système réseau particulier utilisé par le client ou le serveur. Quelques exemples sont donnés ci-dessous. Vous devriez consulter la documentation sur la bibliothèque particulière ou l'ensemble d'API que vous utilisez.

APIs(parameter) Utilisées par l'Application    Valeur Incorrecte  Valeur Correcte  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”

-1voto

utilisant System;
utilisant System.Collections.Generic;
utilisant System.Text;
utilisant System.DirectoryServices.AccountManagement;

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

    public static bool ValidateW(string NomUtilisateur, string MotDePasse)
    {
        bool valide = false;
        string Domaine = "";

        si (NomUtilisateur.IndexOf("\\") != -1)
        {
            string[] arrT = NomUtilisateur.Split(SPLIT_1[0]);
            Domaine = arrT[0];
            NomUtilisateur = arrT[1];
        }

        si (Domaine.Length == 0)
        {
            Domaine = System.Environment.MachineName;
        }

        utilisation (PrincipalContext context = nouveau PrincipalContext(ContextType.Domain, Domaine)) 
        {
            valide = context.ValidateCredentials(NomUtilisateur, MotDePasse);
        }

        retourne valide;
    }
}

Kashif Mushtaq Ottawa, Canada

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X