Wie überprüfe ich Domain-Anmeldeinformationen?

85

Ich möchte eine Reihe von Anmeldeinformationen für den Domänencontroller überprüfen. z.B:

Username: STACKOVERFLOW\joel
Password: splotchy

Methode 1. Active Directory mit Identitätswechsel abfragen

Viele Leute schlagen vor, das Active Directory nach etwas abzufragen. Wenn eine Ausnahme ausgelöst wird, wissen Sie, dass die Anmeldeinformationen ungültig sind - wie in dieser Frage zum Stapelüberlauf vorgeschlagen .

Dieser Ansatz weist jedoch einige schwerwiegende Nachteile auf :

  1. Sie authentifizieren nicht nur ein Domain-Konto, sondern führen auch eine implizite Autorisierungsprüfung durch. Das heißt, Sie lesen Eigenschaften aus dem AD mithilfe eines Identitätswechsel-Tokens. Was ist, wenn das ansonsten gültige Konto keine Leserechte aus dem AD hat? Standardmäßig haben alle Benutzer Lesezugriff, aber Domänenrichtlinien können so eingestellt werden, dass Zugriffsberechtigungen für eingeschränkte Konten (und / oder Gruppen) deaktiviert werden.

  2. Die Bindung an den AD ist mit einem erheblichen Aufwand verbunden. Der AD-Schema-Cache muss auf dem Client geladen werden (ADSI-Cache im ADSI-Anbieter, der von DirectoryServices verwendet wird). Dies ist sowohl netzwerk- als auch AD-Server ressourcenintensiv - und für einen einfachen Vorgang wie die Authentifizierung eines Benutzerkontos zu teuer.

  3. Sie verlassen sich in einem nicht außergewöhnlichen Fall auf einen Ausnahmefehler und gehen davon aus, dass dies einen ungültigen Benutzernamen und ein ungültiges Kennwort bedeutet. Andere Probleme (z. B. Netzwerkfehler, AD-Konnektivitätsfehler, Speicherzuordnungsfehler usw.) werden dann als Authentifizierungsfehler falsch interpretiert.

Methode 2. LogonUser Win32-API

Andere haben vorgeschlagen, die LogonUser()API-Funktion zu verwenden. Das hört sich gut an, aber leider benötigt der anrufende Benutzer manchmal eine Berechtigung, die normalerweise nur dem Betriebssystem selbst erteilt wird:

Der Prozess, der LogonUser aufruft, erfordert die Berechtigung SE_TCB_NAME. Wenn der aufrufende Prozess nicht über diese Berechtigung verfügt, schlägt LogonUser fehl und GetLastError gibt ERROR_PRIVILEGE_NOT_HELD zurück.

In einigen Fällen muss für den Prozess, der LogonUser aufruft, auch die Berechtigung SE_CHANGE_NOTIFY_NAME aktiviert sein. Andernfalls schlägt LogonUser fehl und GetLastError gibt ERROR_ACCESS_DENIED zurück. Diese Berechtigung ist für das lokale Systemkonto oder die Konten, die Mitglieder der Administratorgruppe sind, nicht erforderlich. Standardmäßig ist SE_CHANGE_NOTIFY_NAME für alle Benutzer aktiviert, einige Administratoren können es jedoch für alle Benutzer deaktivieren.

Das Verteilen des Privilegs " Als Teil des Betriebssystems handeln " möchten Sie nicht ohne weiteres tun - wie Microsoft in einem Knowledge Base-Artikel ausführt :

... der Prozess, der LogonUser aufruft, muss über die Berechtigung SE_TCB_NAME verfügen (im Benutzermanager ist dies das Recht " Als Teil des Betriebssystems handeln "). Das SE_TCB_NAME-Privileg ist sehr leistungsfähig und sollte keinem beliebigen Benutzer gewährt werden, nur damit er eine Anwendung ausführen kann , die Anmeldeinformationen validieren muss.

Darüber hinaus schlägt ein Aufruf von LogonUser()fehl, wenn ein leeres Kennwort angegeben wird.


Was ist der richtige Weg, um eine Reihe von Domänenanmeldeinformationen zu authentifizieren?


Ich rufe zufällig von verwaltetem Code aus an, aber dies ist eine allgemeine Windows-Frage. Es kann davon ausgegangen werden, dass auf den Kunden .NET Framework 2.0 installiert ist.

Ian Boyd
quelle
1
Leser sollten beachten, dass LogonUser ab Windows XP SE_TCB_NAME nicht mehr benötigt (es sei denn, Sie melden sich bei einem Passport-Konto an).
Harry Johnston

Antworten:

128

C # in .NET 3.5 mit System.DirectoryServices.AccountManagement .

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

Dies wird gegen die aktuelle Domain validiert. Weitere Optionen finden Sie im parametrisierten PrincipalContext-Konstruktor.

Tvanfosson
quelle
@tvanfosson: Verwendet DirectoryServices kein AD?
Mitch Wheat
1
Ja. Aus der Dokumentation geht jedoch hervor, dass dies eine schnelle Möglichkeit ist, Anmeldeinformationen zu überprüfen. Es unterscheidet sich auch von der in der Frage genannten Bindungsmethode, da Sie keine Eigenschaften aus dem Objekt lesen. Beachten Sie, dass sich die Methode im Kontext befindet und nicht in einem Verzeichnisobjekt.
Tvanfosson
Korrektur: Für System.DirectoryServices.AccountManagement ist .NET 3.5 erforderlich. ( msdn.microsoft.com/en-us/library/… )
Ian Boyd
19
Es funktioniert auch mit lokalen Benutzern, wenn Sie new PrincipalContext(ContextType.Machine)stattdessen verwendet haben.
VansFannel
Weiß jemand, ob dies bei zwischengespeicherten Anmeldeinformationen funktioniert oder ob eine Verbindung zum DC erforderlich ist? Ich muss dies für eine Implementierung wissen, an der ich gerade arbeite, und ich bin derzeit in keiner Domain zum Testen
Jcl
21
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);
        }
    }
}
Kantanomo
quelle
7
Enthält dies einen signifikanten Unterschied zu @ tvanfossons Antwort vor 3 Jahren?
Gbjbaanb
5
@gbjbaanb Ja, da es den DomainParameter beim Erstellen des enthält PrincipalContext, etwas, das ich wissen wollte und das ich in dieser Antwort gefunden habe.
Rudi Visser
1
@RudiVisser tvanfosson hat Ihnen vorgeschlagen, "den parametrisierten PrincipalContext-Konstruktor auf andere Optionen zu überprüfen" - lesen Sie immer die Dokumente, nehmen Sie niemals nur das Wort des Internets für irgendetwas! :)
gbjbaanb
4
@gbjbaanb Ja natürlich, aber die Bereitstellung ein funktionierendes Beispiel , anstatt ein Link und Vorschlag an anderer Stelle zu lesen ist das Mantra Stackoverflow, deshalb haben wir mehrere Anträge von Antworten akzeptieren: D Einfach zu sagen , dass dies nicht mehr geben.
Rudi Visser
Weiß jemand, wie wir in einer UWP-App etwas Ähnliches machen können? (mit normalem AD und nicht mit Azure AD). Ich habe hier eine Frage gestellt: stackoverflow.com/questions/42821447
slayernoah
7

Ich verwende den folgenden Code, um Anmeldeinformationen zu überprüfen. Die unten gezeigte Methode bestätigt, ob die Anmeldeinformationen korrekt sind und ob das Kennwort abgelaufen ist oder geändert werden muss.

Ich habe seit Ewigkeiten nach so etwas gesucht ... Also hoffe ich, dass dies jemandem hilft!

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;
        }
    }
Kevinrr3
quelle
Dies ist "Methode 2", die in der Frage beschrieben wird ... also ... die Frage nicht wirklich beantwortet
Robert Levy
1

So bestimmen Sie einen lokalen Benutzer:

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

Bearbeiten von Ian Boyd

Sie sollten NTLM überhaupt nicht mehr verwenden. Es ist so alt und so schlecht, dass Microsoft Application Verifier (mit dem häufig auftretende Programmierfehler abgefangen werden) eine Warnung ausgibt, wenn Sie NTLM verwenden.

Hier ist ein Kapitel aus der Application Verifier-Dokumentation darüber, warum ein Test durchgeführt wird, wenn jemand NTLM fälschlicherweise verwendet:

Warum das NTLM-Plug-In benötigt wird

NTLM ist ein veraltetes Authentifizierungsprotokoll mit Fehlern, die möglicherweise die Sicherheit von Anwendungen und des Betriebssystems gefährden. Das wichtigste Manko ist das Fehlen einer Serverauthentifizierung, die es einem Angreifer ermöglichen könnte, Benutzer dazu zu verleiten, eine Verbindung zu einem gefälschten Server herzustellen. Als Folge der fehlenden Serverauthentifizierung können Anwendungen, die NTLM verwenden, auch für eine Art von Angriff anfällig sein, der als "Reflection" -Angriff bezeichnet wird. Letzteres ermöglicht es einem Angreifer, die Authentifizierungskonversation eines Benutzers an einen legitimen Server zu entführen und damit den Angreifer beim Computer des Benutzers zu authentifizieren. Die Schwachstellen und Möglichkeiten von NTLM, sie auszunutzen, sind das Ziel einer Steigerung der Forschungstätigkeit in der Sicherheitsgemeinschaft.

Obwohl Kerberos seit vielen Jahren verfügbar ist, sind viele Anwendungen immer noch so geschrieben, dass sie nur NTLM verwenden. Dies verringert unnötig die Sicherheit von Anwendungen. Kerberos kann NTLM jedoch nicht in allen Szenarien ersetzen - hauptsächlich in solchen, in denen sich ein Client bei Systemen authentifizieren muss, die nicht zu einer Domäne gehören (ein Heimnetzwerk ist möglicherweise das häufigste davon). Das Negotiate-Sicherheitspaket ermöglicht einen abwärtskompatiblen Kompromiss, bei dem Kerberos nach Möglichkeit verwendet wird und nur dann auf NTLM zurückgegriffen wird, wenn keine andere Option vorhanden ist. Durch die Umstellung des Codes auf Negotiate anstelle von NTLM wird die Sicherheit für unsere Kunden erheblich erhöht, während nur wenige oder keine Anwendungskompatibilitäten eingeführt werden. Verhandeln an sich ist keine Wunderwaffe - es gibt Fälle, in denen ein Angreifer ein Downgrade auf NTLM erzwingen kann, aber diese sind wesentlich schwieriger auszunutzen. Eine sofortige Verbesserung besteht jedoch darin, dass Anwendungen, die für die korrekte Verwendung von Negotiate geschrieben wurden, automatisch gegen NTLM-Reflexionsangriffe immun sind.

Als letzte Warnung vor der Verwendung von NTLM: In zukünftigen Windows-Versionen kann die Verwendung von NTLM unter dem Betriebssystem deaktiviert werden. Wenn Anwendungen eine starke Abhängigkeit von NTLM haben, können sie sich einfach nicht authentifizieren, wenn NTLM deaktiviert ist.

Wie das Plug-In funktioniert

Der Verifier-Stecker erkennt die folgenden Fehler:

  • Das NTLM-Paket wird direkt im Aufruf von AcquireCredentialsHandle (oder einer Wrapper-API höherer Ebene) angegeben.

  • Der Zielname im Aufruf von InitializeSecurityContext ist NULL.

  • Der Zielname beim Aufruf von InitializeSecurityContext ist kein ordnungsgemäß geformter Domänenname im SPN-, UPN- oder NetBIOS-Stil.

In den beiden letztgenannten Fällen wird Negotiate gezwungen, entweder direkt (im ersten Fall) oder indirekt auf NTLM zurückzugreifen (der Domänencontroller gibt im zweiten Fall den Fehler "Principal not found" zurück, wodurch Negotiate zurückfällt).

Das Plug-In protokolliert auch Warnungen, wenn Downgrades auf NTLM erkannt werden. Zum Beispiel, wenn der Domänencontroller keinen SPN findet. Diese werden nur als Warnungen protokolliert, da es sich häufig um legitime Fälle handelt, z. B. bei der Authentifizierung bei einem System, das nicht einer Domäne beigetreten ist.

NTLM stoppt

5000 - Die Anwendung hat das NTLM-Paket explizit ausgewählt

Schweregrad - Fehler

Die Anwendung oder das Subsystem wählt beim Aufruf von AcquireCredentialsHandle explizit NTLM anstelle von Negotiate aus. Obwohl sich Client und Server möglicherweise mit Kerberos authentifizieren können, wird dies durch die explizite Auswahl von NTLM verhindert.

So beheben Sie diesen Fehler

Die Lösung für diesen Fehler besteht darin, das Negotiate-Paket anstelle von NTLM auszuwählen. Wie dies geschieht, hängt vom jeweiligen Netzwerksubsystem ab, das vom Client oder Server verwendet wird. Einige Beispiele sind unten angegeben. Sie sollten die Dokumentation zu der bestimmten Bibliothek oder dem API-Satz konsultieren, die Sie verwenden.

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  NTLM           NEGOSSP_NAME Negotiate
Alan Nicholas
quelle
-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 Ottawa, Kanada

Markus Safar
quelle
Der System.DirectoryServices.AccountManagement-Namespace war neu in .NET 3.5
Jeremy Gray
1
Ich weiß, dass dies fast 4 Jahre alt ist, aber wenn Sie einen lokalen Benutzer validieren, müssen Sie sicherstellen, dass Sie den ContextType auf ContextType.Machine setzen, wenn Sie einen PrincipalContext erstellen. Andernfalls wird angenommen, dass der in der Domänenvariablen angegebene Computername tatsächlich ein Domänenserver ist.
SolidRegardless