web-dev-qa-db-fra.com

Comment valider les informations d'identification du domaine?

Je souhaite valider un ensemble d'informations d'identification par rapport au contrôleur de domaine. par exemple.:

Username: STACKOVERFLOW\joel
Password: splotchy

Méthode 1. Interroger Active Directory avec emprunt d'identité

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

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

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

  2. La liaison avec l'AD entraîne une surcharge importante, le cache de schéma AD doit être chargé sur le client (cache ADSI du fournisseur ADSI utilisé par DirectoryServices). C’est à la fois un réseau et un serveur AD qui consomme des ressources - et qui 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, en supposant que cela signifie un nom d'utilisateur et un mot de passe invalides. D’autres problèmes (p. Ex. Défaillance du réseau, défaillance de la connectivité AD, erreur d’allocation de mémoire, etc.) sont alors mal interprétés en tant qu’échec d’authentification.

Méthode 2. API Win32 LogonUser

Autres ont suggéré d'utiliser la fonction API LogonUser() . Cela sonne bien, mais malheureusement, l’appelant a parfois besoin d’une autorisation donnée au système d’exploitation lui-même:

Le processus appelant LogonUser nécessite le privilège SE_TCB_NAME. Si le processus appelant ne dispose pas de 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 membres du groupe d'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.

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

... le processus qui appelle LogonUser doit disposer du privilège SE_TCB_NAME (dans le Gestionnaire des utilisateurs, il s'agit du " Agir en tant que partie du système d'exploitation" à droite ". Le privilège SE_TCB_NAME est très puissant et ne doit pas être accordé à un utilisateur arbitraire afin qu'il puisse exécuter une application devant valider les informations d'identification.

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


Quelle est la manière appropriée d'authentifier un ensemble d'informations d'identification de domaine?


Je arrive appeler depuis du code managé, mais il s’agit d’une question générale de Windows. On peut supposer que les clients ont installé le .NET Framework 2.0.

78
Ian Boyd

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

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

Cela validera contre le domaine actuel. Découvrez le constructeur paramétré PrincipalContext pour d'autres options.

119
tvanfosson
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);
        }
    }
}
19
kantanomo

J'utilise le code suivant pour valider les informations d'identification. La méthode présentée ci-dessous confirmera si les informations d'identification sont correctes et si le mot de passe est expiré ou doit être modifié.

Je cherche quelque chose comme ça depuis des lustres ... J'espère donc que cela aidera quelqu'un!

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

Voici comment déterminer un utilisateur local:

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

Edité par Ian Boyd

Vous ne devriez plus utiliser NTLM. Il est si vieux et si mauvais que le vérificateur d'applications de Microsoft (utilisé pour détecter les erreurs de programmation courantes) émettra un avertissement s'il détecte que vous utilisez NTLM.

Voici un chapitre de la documentation d'Application Verifier expliquant pourquoi un test est effectué si quelqu'un utilise NTLM par erreur:

Pourquoi le plug-in NTLM est-il nécessaire?

NTLM est un protocole d'authentification obsolète avec des failles qui peuvent compromettre la sécurité des applications et du système d'exploitation. L'inconvénient le plus important est l'absence d'authentification du serveur, qui pourrait permettre à un attaquant de tromper les utilisateurs en leur demandant de se connecter à un serveur usurpé. En corollaire de l'authentification de serveur manquante, les applications utilisant NTLM peuvent également être vulnérables à un type d'attaque appelé attaque "de réflexion". Ce dernier permet à un attaquant de pirater la conversation d’authentification d’un utilisateur sur un serveur légitime et de l’utiliser pour authentifier l’attaquant sur son ordinateur. Les vulnérabilités de NTLM et les moyens de les exploiter sont l’objectif de l’augmentation des activités de recherche dans la communauté de la sécurité.

Bien que Kerberos soit disponible depuis de nombreuses années, de nombreuses applications sont encore écrites pour utiliser uniquement NTLM. Cela réduit inutilement la sécurité des applications. Kerberos ne peut toutefois pas remplacer NTLM dans tous les scénarios - principalement ceux dans lesquels un client doit s’authentifier auprès de systèmes qui ne sont pas joints à un domaine (un réseau domestique étant peut-être le plus courant). Le package de sécurité Négocier permet un compromis rétrocompatible qui utilise Kerberos lorsque cela est possible et ne revient à NTLM que s’il n’ya pas d’autre option. Changer de code pour utiliser Négocier au lieu de NTLM augmentera considérablement la sécurité de nos clients tout en introduisant peu ou pas de compatibilité d’applications. Négocier en soi n’est pas une solution miracle: il existe des cas dans lesquels un attaquant peut forcer le rétrogradation vers NTLM, mais ceux-ci sont nettement plus difficiles à exploiter. Cependant, une amélioration immédiate réside dans le fait que les applications écrites pour utiliser correctement Negotiate sont automatiquement immunisées contre les attaques par réflexion NTLM.

En guise de mise en garde finale contre l'utilisation de NTLM: dans les futures versions de Windows, il sera possible de désactiver l'utilisation de NTLM sur le système d'exploitation. Si les applications dépendent fortement de NTLM, elles ne pourront tout simplement pas s’authentifier lorsque NTLM est désactivé.

Comment fonctionne le plug-in

La fiche du vérificateur détecte les erreurs suivantes:

  • Le package NTLM est directement spécifié dans l'appel de AcquireCredentialsHandle (ou API 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 domaine de type SPN, UPN ou NetBIOS correctement formé.

Les deux derniers cas obligeront Negotiate à recourir à NTLM, soit directement (le premier cas), soit indirectement (le contrôleur de domaine renvoie une erreur "principal introuvable" dans le second cas, ce qui a entraîné le repli de Negotiate).

Le plug-in consigne également les avertissements lorsqu'il détecte des mises à niveau vers NTLM; Par exemple, lorsqu'un SPN n'est pas trouvé par le contrôleur de domaine. Celles-ci ne sont consignées qu'en tant qu'avertissements, car il s'agit souvent de cas légitimes - par exemple, lors de l'authentification sur un système non lié à un domaine.

Arrêts NTLM

5000 - L’application a explicitement sélectionné le package NTLM

Gravité - Erreur

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

Comment corriger cette erreur

Le correctif de cette erreur consiste à sélectionner le package Negotiate à la place de NTLM. La procédure à suivre dépend du sous-système de réseau utilisé par le client ou le serveur. Quelques exemples sont donnés ci-dessous. Vous devriez consulter la documentation sur la bibliothèque ou le jeu d’API particulier que vous utilisez.

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